Search code examples
c++destructorcopy-constructorcopy-elision

Why is object copy constructed and destructed twice?


Why does an "extra" pair of copy constructor and destruction occur in code below?

It happens when the constructor of Dingledong takes an STL container as an argument (I've tried std::vector and std::list). It may happen for other things? It doesn't happen if the constructor takes a pointer instead. It also doesn't happen if I allocate ding on heap instead( Dingledong* ding = new Dingledong(v) ).

#include <list>
#include <iostream>

class Dingledong{
public:
    Dingledong(std::list<int> numbers)
    {
        std::cout << "construction\n";
        numbers_ = numbers;
    }
    Dingledong(Dingledong const& other)
    {
        std::cout << "COPY construction\n";
    }
    ~Dingledong()
    {
        std::cout << "destructed\n";
        // I would do some cleanup here.. unsubscribe from events, whatever..
        // but the destructor is called sooner than I would expect and thus
        // the cleanup is done prematurely.
    }
    std::list<int> numbers_;
};

void diller()
{
    std::cout << "diller started.. " << std::endl;
    std::list<int> v = std::list<int>(34);
    // Having an STL container as parameter in constructor causes Dingledong's copy constructor to 
    // be used to create a temporary Dingledong which is immediately destructed again. Why?
    Dingledong ding = Dingledong(v);
    std::cout << "Has ding been destructed?\n";
}

int main()
{
    diller();
    system("pause");
}

Output:

diller started...
construction
COPY construction     // I didn't expect this to happen
destructed            // I didn't expect this to happen
Has ding been destructed?
destructed

Thank you in advance!


Solution

  • This code:

    Dingledong ding = Dingledong(v);
    

    means:

    1. Create temporary object of type Dingledong, initialized with v
    2. Create object ding, initialized with the temporary object.

    The copy-constructor comes in step 2 here. If you do not want a copy, don't write code that specifies a copy. For example:

    Dingledong ding(v);   // no copy
    

    Compilers may implement a feature called copy elision where this temporary object is optimized out (even though the copy-constructor has a side-effect), but they don't have to and in this case there is no reason to rely on that.


    You could improve your code by adding a move-constructor, in which case (if the compiler does not perform copy elision) the operation would be a move instead of a copy, which is less expensive.

    There are also a wasted copy in your constructor (numbers is copied from v, and then numbers_ is copied from numbers, and also numbers_ is initialized and then assigned, instead of just being initialized). This would be a much better constructor:

    Dingledong(std::list<int> numbers): numbers_( std::move(numbers) )
    {
        std::cout << "construction\n";
    }