qué que copia c++ visual-c++ constructor copy-constructor

que - ¿En qué situaciones se llama el constructor de copia C++?



que es un destructor en c++ (7)

Cuando a un objeto existente se le asigna un objeto de su propia clase

B = A;

No necesariamente. Este tipo de asignación se llama copia-asignación , lo que significa que se llamará al operador de asignación de la clase para realizar la asignación de todos los miembros de datos. La función real es MyClass& operator=(MyClass const&)

El copy-constructor no se invoca aquí . Esto se debe a que el operador de asignación toma una referencia a su objeto y, por lo tanto, no se realiza una construcción de copia.

La asignación de copia es diferente de la inicialización de copia porque la inicialización de la copia solo se realiza cuando se inicializa un objeto. Por ejemplo:

T y = x; x = y;

La primera expresión inicializa y copiando x . Invoca el constructor de copias MyClass(MyClass const&) .

Y como se mencionó, x = y es una llamada al operador de asignación.

(También hay algo llamado copy-elison por el cual el compilador copy-elison las llamadas al constructor de copias. Es muy probable que el compilador lo use).

Si a funciones recibe como argumento, pasado por valor, un objeto de una clase

void foo(MyClass a); foo(a);

Esto es correcto. Sin embargo, tenga en cuenta que en C ++ 11 si a es un valor xy si MyClass tiene el constructor apropiado MyClass(MyClass&&) , a se puede moved al parámetro.

(El constructor de copias y el constructor de movimientos son dos de las funciones miembro generadas por el compilador por defecto de una clase. Si no las proporciona usted mismo, el compilador lo hará generosamente por usted en circunstancias específicas).

Cuando una función devuelve (por valor) un objeto de la clase

MyClass foo () { MyClass temp; .... return temp; // copy constructor called }

A través de la optimización del valor de retorno , como se menciona en algunas de las respuestas, el compilador puede eliminar la llamada al constructor de copias. Al usar la opción del compilador -fno-elide-constructors , puede desactivar copy-elison y ver que el constructor de copia sería llamado en estas situaciones.

Sé de las siguientes situaciones en c ++ donde se invocaría el constructor de copia:

  1. cuando a un objeto existente se le asigna un objeto de su propia clase

    MyClass A,B; A = new MyClass(); B=A; //copy constructor called

  2. si a funciones recibe como argumento, pasado por valor, un objeto de una clase

    void foo(MyClass a); foo(a); //copy constructor invoked

  3. cuando una función devuelve (por valor) un objeto de la clase

    MyClass foo () { MyClass temp; .... return temp; //copy constructor called }

Por favor, siéntase libre de corregir cualquier error que haya cometido; pero tengo más curiosidad si hay otras situaciones en las que se llama al constructor de copias.


Esto es básicamente correcto (aparte de tu error tipográfico en el n. ° 1).

Un escenario específico adicional a tener en cuenta es que cuando tiene elementos en un contenedor, los elementos se pueden copiar varias veces (por ejemplo, en un vector, cuando el vector crece o se eliminan algunos elementos). Esto es solo un ejemplo de # 1, pero puede ser fácil olvidarlo.


Hay 3 situaciones en las que se llama al constructor de copias: cuando hacemos una copia de un objeto. Cuando pasamos un objeto como argumento por valor a un método. Cuando devolvemos un objeto de un método por valor.

estas son las únicas situaciones ... creo ...


La situación (1) es incorrecta y no compila de la forma en que la escribió. Debería ser:

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.

Estás en lo correcto en el caso (2).

Pero en el caso (3), no se puede invocar al constructor de copias: si el compilador no puede detectar efectos secundarios, puede implementar la optimización del valor de retorno para optimizar la copia profunda innecesaria. C ++ 11 formaliza esto con referencias rvalue .


Los siguientes son los casos cuando se llama al constructor de copia.

  1. Al crear instancias de un objeto e inicializarlo con valores de otro objeto.
  2. Al pasar un objeto por valor.
  3. Cuando un objeto se devuelve de una función por valor.

Otros han proporcionado buenas respuestas, con explicaciones y referencias.

Además, he escrito una clase para verificar los diferentes tipos de instantaciones / asignaciones (C ++ 11 listo), dentro de una extensa prueba:

#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;

Aquí está la prueba:

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 ); }

Este es un resumen de la prueba compilada con GCC 4.8.2 con -fno-elide-constructors y -fno-elide-constructors :

Llamadas normales al constructor: 10
Copiar llamadas de constructor: 2
Mover llamadas de constructor: 11
Copiar llamadas de asignación: 1
Mover llamadas de asignación: 2
Destructor llama: 19

Instantaciones totales: 23
Destrucciones totales: 19
Instancias activas actuales: 4

Finalmente, la misma prueba con copia elision habilitada:

Llamadas normales al constructor: 10
Copiar llamadas de constructor: 2
Mover llamadas de constructor: 3
Copiar llamadas de asignación: 1
Mover llamadas de asignación: 2
Destructor llama: 11

Instantaciones totales: 15
Destrucciones totales: 11
Instancias activas actuales: 4

Here está el código completo que se ejecuta en ideone.


Puede que me equivoque al respecto, pero esta clase te permite ver lo que se llama y cuándo:

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; }; };

Entonces este código:

a b; //constructor a c; //constructor b = c; //copy assignment c = a(b); //copy constructor, then copy assignment

produce esto como el resultado:

constructor called constructor called copy assignment operator called copy constructor called copy assignment operator called

Otra cosa interesante, digamos que tienes el siguiente código:

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

Esto ocurre porque cuando le asigna un puntero, eso no le hace nada al objeto real.