W jakich sytuacjach nazywa się Konstruktor kopiujący C++?
Znam następujące sytuacje w c++, w których wywoływany byłby Konstruktor kopiujący:
-
Gdy istniejącemu obiektowi przypisany jest obiekt jego własnej klasy
MyClass A,B; A = new MyClass(); B=A; //copy constructor called
-
Jeśli funkcja otrzymuje jako argument, przekazywany przez wartość, obiekt klasy
void foo(MyClass a); foo(a); //copy constructor invoked
-
Gdy funkcja zwraca (przez wartość) obiekt klasy
MyClass foo () { MyClass temp; .... return temp; //copy constructor called }
7 answers
Mogę się mylić co do tego, ale ta klasa pozwala zobaczyć co się nazywa i kiedy:
class a {
public:
a() {
printf("constructor called\n");
};
a(const a& other) {
printf("copy constructor called\n");
};
a& operator=(const a& other) {
printf("copy assignment operator called\n");
return *this;
};
};
Więc ten kod:
a b; //constructor
a c; //constructor
b = c; //copy assignment
c = a(b); //copy constructor, then copy assignment
Tworzy to w wyniku:
constructor called
constructor called
copy assignment operator called
copy constructor called
copy assignment operator called
Kolejna ciekawa rzecz, powiedzmy, że masz następujący kod:
a* b = new a(); //constructor called
a* c; //nothing is called
c = b; //still nothing is called
c = new a(*b); //copy constructor is called
Dzieje się tak, ponieważ gdy przypisujesz wskaźnik, to nic nie robi rzeczywistemu obiektowi.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-18 16:07:49
Niekoniecznie. Ten rodzaj przypisania nazywa się copy-assignment , co oznacza, że operator przypisania klasy zostanie wywołany do wykonania przypisania memberwise wszystkich członków danych. Funkcja rzeczywista toGdy istniejącemu obiektowi przypisany jest obiekt jego własnej klasy
B = A;
MyClass& operator=(MyClass const&)
Konstruktor kopiujący nie jest tutaj wywoływany . Dzieje się tak dlatego, że operator przypisania przyjmuje odniesienie do swojego obiektu, a zatem nie kopiowanie-konstrukcja jest wykonywana.
Copy-assignment różni się od copy-initialization ponieważ copy-initialization odbywa się tylko wtedy, gdy obiekt jest inicjowany. Na przykład:
T y = x;
x = y;
Pierwsze wyrażenie inicjalizuje y
przez skopiowanie x
. Wywołuje konstruktor kopii MyClass(MyClass const&)
.
I jak wspomniano, {[8] } jest wywołaniem do operatora przypisania.
(jest też coś, co nazywa się copy-elison przy czym kompilator wymknie się wywołaniom konstruktora kopiującego. Twój kompilator prawdopodobnie tego używa).
Jeśli funkcja otrzymuje jako argument, przekazywany przez wartość, obiekt klasy
void foo(MyClass a); foo(a);
To jest poprawne. Należy jednak pamiętać, że w C++11 jeśli a
jest wartością xvalue i jeśli MyClass
ma odpowiedni konstruktor MyClass(MyClass&&)
, a
może być przeniesiony do parametru.
(copy-constructor I move-constructor to dwa z domyślnie generowane przez kompilator funkcje klasy. Jeśli nie dostarczysz ich samodzielnie, kompilator hojnie zrobi to za Ciebie w określonych okolicznościach).
Gdy funkcja zwraca (przez wartość) obiekt klasy
MyClass foo () { MyClass temp; .... return temp; // copy constructor called }
Poprzez optymalizację zwracanej wartości , Jak wspomniano w niektórych odpowiedziach, kompilator może usunąć wywołanie do konstruktora kopiującego. Korzystając z opcji kompilatora-fno-elide-constructors
, możesz wyłącz copy-elison i sprawdź, czy Konstruktor kopiujący rzeczywiście zostanie wywołany w takich sytuacjach.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:08:44
Situation (1) jest niepoprawne i nie kompiluje się w sposób, w jaki ją napisałeś. Powinno być:
MyClass A, B;
A = MyClass(); /* Redefinition of `A`; perfectly legal though superfluous: I've
dropped the `new` to defeat compiler error.*/
B = A; // Assignment operator called (`B` is already constructed)
MyClass C = B; // Copy constructor called.
Masz rację w przypadku (2).
Ale w przypadku (3) Konstruktor kopiujący może nie zostać wywołany: jeśli kompilator nie wykryje żadnych efektów ubocznych, może zaimplementować return value optimization, aby zoptymalizować niepotrzebną głęboką kopię. C++11 formalizuje to za pomocą rvalue reference .
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-18 16:07:22
Istnieją 3 sytuacje, w których Konstruktor kopiujący jest wywoływany: Kiedy robimy kopię obiektu. Gdy przekazujemy obiekt jako argument przez wartość do metody. Gdy zwracamy obiekt z metody przez wartość.
To jedyne sytuacje....tak myślę...
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-18 16:03:19
To jest w zasadzie poprawne(poza literówką w #1).
Dodatkowym, specyficznym scenariuszem, na który należy uważać, jest sytuacja, gdy elementy są w kontenerze, elementy mogą być kopiowane w różnym czasie(na przykład w wektorze, gdy wektor rośnie lub niektóre elementy są usuwane). To właściwie tylko przykład #1, ale łatwo o tym zapomnieć.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-18 16:09:25
Poniżej przedstawiono przypadki wywołania konstruktora kopiującego.
- podczas tworzenia instancji jednego obiektu i inicjalizacji go wartościami z innego obiektu.
- podczas przekazywania obiektu przez wartość.
- gdy obiekt jest zwracany z funkcji przez wartość.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-18 16:05:43
Inni udzielili dobrych odpowiedzi, wraz z wyjaśnieniami i odniesieniami.
Dodatkowo napisałem klasę, aby sprawdzić różne typy instancji / asystentów (C++11 ready), w ramach obszernego testu:
#include <iostream>
#include <utility>
#include <functional>
template<typename T , bool MESSAGES = true>
class instantation_profiler
{
private:
static std::size_t _alive , _instanced , _destroyed ,
_ctor , _copy_ctor , _move_ctor ,
_copy_assign , _move_assign;
public:
instantation_profiler()
{
_alive++;
_instanced++;
_ctor++;
if( MESSAGES ) std::cout << ">> construction" << std::endl;
}
instantation_profiler( const instantation_profiler& )
{
_alive++;
_instanced++;
_copy_ctor++;
if( MESSAGES ) std::cout << ">> copy construction" << std::endl;
}
instantation_profiler( instantation_profiler&& )
{
_alive++;
_instanced++;
_move_ctor++;
if( MESSAGES ) std::cout << ">> move construction" << std::endl;
}
instantation_profiler& operator=( const instantation_profiler& )
{
_copy_assign++;
if( MESSAGES ) std::cout << ">> copy assigment" << std::endl;
}
instantation_profiler& operator=( instantation_profiler&& )
{
_move_assign++;
if( MESSAGES ) std::cout << ">> move assigment" << std::endl;
}
~instantation_profiler()
{
_alive--;
_destroyed++;
if( MESSAGES ) std::cout << ">> destruction" << std::endl;
}
static std::size_t alive_instances()
{
return _alive;
}
static std::size_t instantations()
{
return _instanced;
}
static std::size_t destructions()
{
return _destroyed;
}
static std::size_t normal_constructions()
{
return _ctor;
}
static std::size_t move_constructions()
{
return _move_ctor;
}
static std::size_t copy_constructions()
{
return _copy_ctor;
}
static std::size_t move_assigments()
{
return _move_assign;
}
static std::size_t copy_assigments()
{
return _copy_assign;
}
static void print_info( std::ostream& out = std::cout )
{
out << "# Normal constructor calls: " << normal_constructions() << std::endl
<< "# Copy constructor calls: " << copy_constructions() << std::endl
<< "# Move constructor calls: " << move_constructions() << std::endl
<< "# Copy assigment calls: " << copy_assigments() << std::endl
<< "# Move assigment calls: " << move_assigments() << std::endl
<< "# Destructor calls: " << destructions() << std::endl
<< "# " << std::endl
<< "# Total instantations: " << instantations() << std::endl
<< "# Total destructions: " << destructions() << std::endl
<< "# Current alive instances: " << alive_instances() << std::endl;
}
};
template<typename T , bool MESSAGES>
std::size_t instantation_profiler<T,MESSAGES>::_alive = 0;
template<typename T , bool MESSAGES>
std::size_t instantation_profiler<T,MESSAGES>::_instanced = 0;
template<typename T , bool MESSAGES>
std::size_t instantation_profiler<T,MESSAGES>::_destroyed = 0;
template<typename T , bool MESSAGES>
std::size_t instantation_profiler<T,MESSAGES>::_ctor = 0;
template<typename T , bool MESSAGES>
std::size_t instantation_profiler<T,MESSAGES>::_copy_ctor = 0;
template<typename T , bool MESSAGES>
std::size_t instantation_profiler<T,MESSAGES>::_move_ctor = 0;
template<typename T , bool MESSAGES>
std::size_t instantation_profiler<T,MESSAGES>::_copy_assign = 0;
template<typename T , bool MESSAGES>
std::size_t instantation_profiler<T,MESSAGES>::_move_assign = 0;
Oto test:
struct foo : public instantation_profiler<foo>
{
int value;
};
//Me suena bastante que Boost tiene una biblioteca con una parida de este estilo...
struct scoped_call
{
private:
std::function<void()> function;
public:
scoped_call( const std::function<void()>& f ) : function( f ) {}
~scoped_call()
{
function();
}
};
foo f()
{
scoped_call chapuza( [](){ std::cout << "Exiting f()..." << std::endl; } );
std::cout << "I'm in f(), which returns a foo by value!" << std::endl;
return foo();
}
void g1( foo )
{
scoped_call chapuza( [](){ std::cout << "Exiting g1()..." << std::endl; } );
std::cout << "I'm in g1(), which gets a foo by value!" << std::endl;
}
void g2( const foo& )
{
scoped_call chapuza( [](){ std::cout << "Exiting g2()..." << std::endl; } );
std::cout << "I'm in g2(), which gets a foo by const lvalue reference!" << std::endl;
}
void g3( foo&& )
{
scoped_call chapuza( [](){ std::cout << "Exiting g3()..." << std::endl; } );
std::cout << "I'm in g3(), which gets an rvalue foo reference!" << std::endl;
}
template<typename T>
void h( T&& afoo )
{
scoped_call chapuza( [](){ std::cout << "Exiting h()..." << std::endl; } );
std::cout << "I'm in h(), which sends a foo to g() through perfect forwarding!" << std::endl;
g1( std::forward<T>( afoo ) );
}
int main()
{
std::cout << std::endl << "Just before a declaration ( foo a; )" << std::endl; foo a;
std::cout << std::endl << "Just before b declaration ( foo b; )" << std::endl; foo b;
std::cout << std::endl << "Just before c declaration ( foo c; )" << std::endl; foo c;
std::cout << std::endl << "Just before d declaration ( foo d( f() ); )" << std::endl; foo d( f() );
std::cout << std::endl << "Just before a to b assigment ( b = a )" << std::endl; b = a;
std::cout << std::endl << "Just before ctor call to b assigment ( b = foo() )" << std::endl; b = foo();
std::cout << std::endl << "Just before f() call to b assigment ( b = f() )" << std::endl; b = f();
std::cout << std::endl << "Just before g1( foo ) call with lvalue arg ( g1( a ) )" << std::endl; g1( a );
std::cout << std::endl << "Just before g1( foo ) call with rvalue arg ( g1( f() ) )" << std::endl; g1( f() );
std::cout << std::endl << "Just before g1( foo ) call with lvalue ==> rvalue arg ( g1( std::move( a ) ) )" << std::endl; g1( std::move( a ) );
std::cout << std::endl << "Just before g2( const foo& ) call with lvalue arg ( g2( b ) )" << std::endl; g2( b );
std::cout << std::endl << "Just before g2( const foo& ) call with rvalue arg ( g2( f() ) )" << std::endl; g2( f() );
std::cout << std::endl << "Just before g2( const foo& ) call with lvalue ==> rvalue arg ( g2( std::move( b ) ) )" << std::endl; g2( std::move( b ) );
//std::cout << std::endl << "Just before g3( foo&& ) call with lvalue arg ( g3( c ) )" << std::endl; g3( c );
std::cout << std::endl << "Just before g3( foo&& ) call with rvalue arg ( g3( f() ) )" << std::endl; g3( f() );
std::cout << std::endl << "Just before g3( foo&& ) call with lvalue ==> rvalue arg ( g3( std::move( c ) ) )" << std::endl; g3( std::move( c ) );
std::cout << std::endl << "Just before h() call with lvalue arg ( h( d ) )" << std::endl; h( d );
std::cout << std::endl << "Just before h() call with rvalue arg ( h( f() ) )" << std::endl; h( f() );
std::cout << std::endl << "Just before h() call with lvalue ==> rvalue arg ( h( std::move( d ) ) )" << std::endl; h( std::move( d ) );
foo::print_info( std::cout );
}
Jest to streszczenie testu zestawione z GCC 4.8.2
z -O3
i -fno-elide-constructors
flagami:
Normalne wywołania konstruktora: 10
Wywołania konstruktora kopiującego: 2
Wywołania konstruktora Move: 11
Copy assigment wywołania: 1
Move assigment calls: 2
Wywołania destruktora: 19Suma wejść: 23
Total destructions: 19
Current alive instances: 4
Wreszcie ten sam test z włączonym copy elision:
Normalne wywołania konstruktora: 10
Wywołania konstruktora kopiującego: 2
Wywołania konstruktora Move: 3
Copy assigment calls: 1
Move assigment calls: 2
Wywołania destruktora: 11Całkowita instancja: 15
Total destructions: 11
Current alive instances: 4
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-18 20:28:05