Je connais les situations suivantes en c ++ où le constructeur de copie serait invoqué:
lorsqu'un objet existant se voit attribuer un objet de sa propre classe
MyClass A,B;
A = new MyClass();
B=A; //copy constructor called
si une fonction reçoit comme argument, passé par valeur, un objet d'une classe
void foo(MyClass a);
foo(a); //copy constructor invoked
lorsqu'une fonction retourne (par valeur) un objet de la classe
MyClass foo ()
{
MyClass temp;
....
return temp; //copy constructor called
}
N'hésitez pas à corriger les erreurs que j'ai commises; mais je suis plus curieux s'il y a d'autres situations dans lesquelles le constructeur de copie est appelé.
Je me trompe peut-être, mais cette classe vous permet de voir ce qui est appelé et quand:
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;
};
};
Alors, ce code:
a b; //constructor
a c; //constructor
b = c; //copy assignment
c = a(b); //copy constructor, then copy assignment
produit ceci comme résultat:
constructor called
constructor called
copy assignment operator called
copy constructor called
copy assignment operator called
Autre chose intéressante, disons que vous avez le code suivant:
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
Cela se produit parce que lorsque vous affectez un pointeur, cela ne fait rien à l'objet réel.
Lorsqu'un objet existant se voit attribuer un objet de sa propre classe
B = A;
Pas nécessairement. Ce type d'affectation est appelé copy-assignation, ce qui signifie que l'opérateur d'affectation de la classe sera appelé pour effectuer l'affectation par membre de tous les membres de données. La fonction réelle est MyClass& operator=(MyClass const&)
Le constructeur de copie n'est pas appelé ici . En effet, l'opérateur d'affectation prend une référence à son objet et, par conséquent, aucune construction de copie n'est effectuée.
L'affectation de copie est différente de initialisation de copie car l'initialisation de copie n'est effectuée que lorsqu'un objet est en cours d'initialisation. Par exemple:
T y = x;
x = y;
La première expression initialise y
en copiant x
. Il appelle le constructeur de copie MyClass(MyClass const&)
.
Et comme mentionné, x = y
Est un appel à l'opérateur d'affectation.
(Il y a aussi quelque chose appelé copy-elison par lequel le compilateur élide les appels au constructeur de copie. Votre compilateur l'utilise plus que probablement).
Si une fonction reçoit comme argument, passé par valeur, un objet d'une classe
void foo(MyClass a); foo(a);
C'est correct. Cependant, notez qu'en C++ 11 si a
est une valeur x et si MyClass
a le constructeur approprié MyClass(MyClass&&)
, a
peut être déplacé dans le paramètre.
(Le constructeur de copie et le constructeur de déplacement sont deux des fonctions membres générées par compilateur par défaut d'une classe. Si vous ne les fournissez pas vous-même, le compilateur le fera généreusement pour vous dans des circonstances spécifiques).
Lorsqu'une fonction retourne (par valeur) un objet de la classe
MyClass foo () { MyClass temp; .... return temp; // copy constructor called }
Grâce à optimisation de la valeur de retour , comme mentionné dans certaines réponses, le compilateur peut supprimer l'appel au constructeur de copie. En utilisant l'option du compilateur -fno-elide-constructors
, vous pouvez désactiver copy-elison et voir que le constructeur de copie serait en effet appelé dans ces situations.
La situation (1) est incorrecte et ne compile pas la façon dont vous l'avez écrite. Ça devrait être:
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.
Vous avez raison au cas (2).
Mais dans le cas (3), le constructeur de copie ne peut pas être appelé: si le compilateur ne détecte aucun effet secondaire, il peut implémenter optimisation de la valeur de retour pour optimiser la copie profonde inutile. C++ 11 formalise cela avec références rvalue.
C'est fondamentalement correct (autre que votre faute de frappe dans # 1).
Un scénario spécifique supplémentaire à surveiller est lorsque vous avez des éléments dans un conteneur, les éléments peuvent être copiés à différents moments (par exemple, dans un vecteur, lorsque le vecteur se développe ou que certains éléments sont supprimés). Ce n'est en fait qu'un exemple de # 1, mais il peut être facile de l'oublier.
Il y a 3 situations dans lesquelles le constructeur de copie est appelé: Lorsque nous faisons une copie d'un objet. Lorsque nous passons un objet comme argument par valeur à une méthode. Lorsque nous renvoyons un objet d'une méthode par valeur.
ce sont les seules situations .... je pense ...
Voici les cas où le constructeur de copie est appelé.
D'autres ont fourni de bonnes réponses, avec des explications et des références.
De plus, j'ai écrit une classe pour vérifier les différents types d'instanciations/assignations (prêt pour C++ 11), dans le cadre d'un test approfondi:
#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;
Voici le 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 );
}
Ceci est un résumé du test compilé avec GCC 4.8.2
avec -O3
et -fno-elide-constructors
drapeaux:
Appels de constructeur normaux: 10
Copier les appels du constructeur: 2
Déplacer les appels du constructeur: 11
Copie des appels d'assignation: 1
Déplacer les appels d'affectation: 2
Appels du destructeur: 19Nombre total d'instanciations: 23
Total des destructions: 19
Instances vivantes actuelles: 4
Enfin le même test avec copie élision activée:
Appels de constructeur normaux: 10
Copier les appels du constructeur: 2
Déplacer les appels du constructeur: 3
Copie des appels d'assignation: 1
Déplacer les appels d'affectation: 2
Appels du destructeur: 11Nombre total d'instanciations: 15
Total des destructions: 11
Instances vivantes actuelles: 4
Ici est le code complet exécuté sur ideone.