36

I know of the following situations in c++ where the copy constructor would be invoked:

  1. when an existing object is assigned an object of it own class

    MyClass A,B;
    A = new MyClass();
    B=A; //copy constructor called 
    
  2. if a functions receives as argument, passed by value, an object of a class

    void foo(MyClass a);
    foo(a); //copy constructor invoked
    
  3. when a function returns (by value) an object of the class

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

Please feel free to correct any mistakes I've made; but I am more curious if there are any other situations in which the copy constructor is called.

Joseph Mansfield
  • 108,238
  • 20
  • 242
  • 324
Pandrei
  • 4,843
  • 3
  • 27
  • 44
  • 14
    I thought `A=B;` calls the copy assignment operator. – BWG Jan 18 '14 at 15:55
  • 6
    Also read about return value optimization (RVO), your last example might not copy anything. – Mat Jan 18 '14 at 16:04
  • 12
    Besides, `A = new MyClass();` won't compile. – Andy Prowl Jan 18 '14 at 16:05
  • 3
    This is not valid C++. – Lightness Races in Orbit Jan 18 '14 at 16:07
  • 3
    @BWG, only it's done **after** the declaration of `A`. For example: `A a; ... a=b;`. If it's done at the declaration itself, then `A a=b` is equivalent to `A a(b)`. – barak manos Jan 18 '14 at 16:20
  • @barakmanos interesting. I guess it wouldn't make sense to construct it, delete it, then copy it. – BWG Jan 18 '14 at 16:25
  • I rolled back to the previous version of the question because answers and comments were based on it. – Joseph Mansfield Jan 18 '14 at 16:32
  • @barakmanos: Actually, `A a = b;` is equivalent to `A a(A(b));` if I recall correctly, and therefore requires an accessible copy constructor on top of an implicit conversion. – Matthieu M. Jan 18 '14 at 16:40
  • Excuse me, but your java is showing. – John Dibling Jan 18 '14 at 16:55
  • @Andy Prowl Couldn't it compile if `MyClass` had an overload of the assignment operator taking an `MyClass*` as its argument? – Zyx 2000 Jan 18 '14 at 22:54
  • @Zyx2000: Ah, nice (or rather, ugly!) ;) Yes it would, however there's no reason to consider that possibility here, as the OP is mostly concerned with the basics of copying and assignment. But yes, your observation is correct. – Andy Prowl Jan 19 '14 at 00:13
  • possible duplicate of [C++ copy constructor behaviour](http://stackoverflow.com/questions/20874193/c-copy-constructor-behaviour) – jww Feb 06 '14 at 08:50

7 Answers7

37

When an existing object is assigned an object of it own class

    B = A;

Not necessarily. This kind of assignment is called copy-assignment, meaning the assignment operator of the class will be called to perform memberwise assignment of all the data members. The actual function is MyClass& operator=(MyClass const&)

The copy-constructor is not invoked here. This is because the assignment operator takes a reference to its object, and therefore no copy-construction is performed.

Copy-assignment is different from copy-initialization because copy-initialization is only done when an object is being initialized. For example:

T y = x;
  x = y;

The first expression initializes y by copying x. It invokes the copy-constructor MyClass(MyClass const&).

And as mentioned, x = y is a call to the assignment operator.

(There is also something called copy-elison whereby the compiler will elide calls to the copy-constructor. Your compiler more than likely uses this).


If a functions receives as argument, passed by value, an object of a class

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

This is correct. However, note that in C++11 if a is an xvalue and if MyClass has the appropriate constructor MyClass(MyClass&&), a can be moved into the parameter.

(The copy-constructor and the move-constructor are two of the default compiler-generated member functions of a class. If you do not supply them yourself, the compiler will generously do so for you under specific circumstances).


When a function returns (by value) an object of the class

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

Through return-value optimization, as mentioned in some of the answers, the compiler can remove the call to the copy-constructor. By using the compiler option -fno-elide-constructors, you can disable copy-elison and see that the copy-constructor would indeed be called in these situations.

Community
  • 1
  • 1
David G
  • 94,763
  • 41
  • 167
  • 253
  • I don't think the last example is true. "return temp" will not call copy constructor, but if you add "MyClass & ref = temp;" and "return ref;", this time the copy constructor will be called. – chenlian Jan 18 '16 at 07:28
  • 3
    @chenlian Now that I'm coming back to this answer, I find that it's a bit inaccurate. If `-fno-elide-constructors` isn't enabled then it's actually the *move-constructor* that is called first if it's available, and if not the copy-constructor is called. The reason `MyClass& ref=temp; return ref` calls the copy-constructor is because return value optimization requires an id-expression. You'd need an explicit `std::move` in that case. – David G Jan 18 '16 at 16:17
  • upvote for `-fno-elide-constructors`. Without it some of mine tests can never match my assumption. – Rick Apr 16 '20 at 14:58
25

I might be wrong about this, but this class allows you to see what is called and when:

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

So then this code:

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

produces this as the result:

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

Another interesting thing, say you have the following code:

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

This occurs because when you when you assign a pointer, that does nothing to the actual object.

BWG
  • 2,238
  • 1
  • 20
  • 32
  • 11
    there's one more `a c = b;` also calls copy constructor – prajmus Jan 18 '14 at 16:04
  • 1
    Don't forget passing objects by value for arguments, or returning objects by value. – Some programmer dude Jan 18 '14 at 16:06
  • 2
    My code was not meant to demonstrate all possible events, it shows a class that can be used to see events. – BWG Jan 18 '14 at 16:10
  • @BWG what about `c = a + b;` `c` is already declared, and the overloaded operator+ is returning object by value. Which will get called? copy assignment or copy constructor or both. I am confused. – Swapnil Mar 13 '18 at 16:41
  • 1
    @Swapnil I think it should be the copy assignment operator, because you are using the = operator. As far as I'm aware, if you use the = operator, it always calls operator=, unless it is first time initialization. – BWG Apr 06 '18 at 18:30
  • this also triggers copy constructor: a b=c; – Nguai al Jun 20 '18 at 14:51
  • 1
    If you need to test vector behaviour though, when you declare copy constructor (and assignment oper) like this, then **move** constructor (and assignment oper) are not defined by default by the compiler! So there are cases that a move constructor might have been preferred against copy. but you will not be able to tell, because this way the copy constructor will always be called. – Vassilis Dec 22 '18 at 23:37
12

Situation (1) is incorrect and does not compile the way you've written it. It should be:

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.

You are correct in case (2).

But in case (3), the copy constructor may not be called: if the compiler can detect no side effects then it can implement return value optimisation to optimise out the unnecessary deep copy. C++11 formalises this with rvalue references.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
6

This is basically correct (other than your typo in #1).

One additional specific scenario to watch out for is when you have elements in a container, the elements may be copied at various times (for example, in a vector, when the vector grows or some elements are removed). This is actually just an example of #1, but it can be easy to forget about it.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
5

There are 3 situations in which the copy constructor is called: When we make copy of an object. When we pass an object as an argument by value to a method. When we return an object from a method by value.

these are the only situations....i think...

Akshay
  • 790
  • 2
  • 8
  • 22
4

The following are the cases when copy constructor is called.

  1. When instantiating one object and initializing it with values from another object.
  2. When passing an object by value.
  3. When an object is returned from a function by value.
leshy84
  • 680
  • 5
  • 13
2

Others have provided good answers, with explanations and references.

In addition, I have written a class to check the different type of instantations/assigments (C++11 ready), within an extensive test:

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

Here is the 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 );
}

This is an abstract of the test compiled with GCC 4.8.2 with -O3 and -fno-elide-constructors flags:

Normal constructor calls: 10
Copy constructor calls: 2
Move constructor calls: 11
Copy assigment calls: 1
Move assigment calls: 2
Destructor calls: 19

Total instantations: 23
Total destructions: 19
Current alive instances: 4

Finally the same test with copy elision enabled:

Normal constructor calls: 10
Copy constructor calls: 2
Move constructor calls: 3
Copy assigment calls: 1
Move assigment calls: 2
Destructor calls: 11

Total instantations: 15
Total destructions: 11
Current alive instances: 4

Here is the complete code running at ideone.

Manu343726
  • 13,969
  • 4
  • 40
  • 75