web-dev-qa-db-fra.com

Dans quelles situations le constructeur de copie C ++ est-il appelé?

Je connais les situations suivantes en c ++ où le constructeur de copie serait invoqué:

  1. lorsqu'un objet existant se voit attribuer un objet de sa propre classe

    MyClass A,B;
    A = new MyClass();
    B=A; //copy constructor called 
    
  2. si une fonction reçoit comme argument, passé par valeur, un objet d'une classe

    void foo(MyClass a);
    foo(a); //copy constructor invoked
    
  3. 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é.

30
Pandrei

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.

20
BWG

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.

24
0x499602D2

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.

12
Bathsheba

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.

6

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 ...

5
Akshay

Voici les cas où le constructeur de copie est appelé.

  1. Lors de l'instanciation d'un objet et de son initialisation avec les valeurs d'un autre objet.
  2. Lors du passage d'un objet par valeur.
  3. Lorsqu'un objet est renvoyé d'une fonction par valeur.
3
leshy84

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: 19

Nombre 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: 11

Nombre total d'instanciations: 15
Total des destructions: 11
Instances vivantes actuelles: 4

Ici est le code complet exécuté sur ideone.

2
Manu343726