Tobi S.
Tobi S.

Reputation: 183

Unique pointer - Why is the destructor called 3 times

I have a method that returns an object by value. The method comes from a library that I have no control over. For the further handling of the object I want to continue working with a unique_ptr on this object. Here is an example:

#include <iostream>
#include <memory>

class Bla {
  public:
      Bla() { std::cout << "Constructor!\n"; }
      ~Bla() { std::cout << "Destructor!\n"; }
};

Bla GetBla() {
  Bla bla;
  return std::move(bla);
}

int main() {
  auto bla = std::make_unique<Bla>(GetBla());
}

The example produces the following output:

Constructor!
Destructor!
Destructor!
Destructor!

Why is the destructor of Bla called 3 times here? Is the way I create the unique_prt correct?

Upvotes: 8

Views: 1775

Answers (4)

abhiarora
abhiarora

Reputation: 10430

To really see what's happening behind the scenes, you can either use a debugger or define copy constructor. I have added the copy constructor in your code. Try the code given below:

#include <iostream>
#include <memory>

class Bla {
public:
    Bla(void) 
    {
        std::cout << "Constructor!" << std::endl;
    }
    //Bla(Bla &&)
    //{
    //    std::cout << "Move Constructors" << std::endl;
    //}
    Bla(const Bla &)
    {
        std::cout << "Copy Constructors" << std::endl;
    }
    ~Bla(void)
    {
        std::cout << "Destructor!" << std::endl;
    }
private:
    int a = 2;
};

Bla GetBla(void) 
{
    Bla bla; // Default Constructor Called here
    return std::move(bla); // Second Construction over here
}

int main(void)
{
    auto bla = std::make_unique<Bla>(GetBla()); // Third Construction
    return 0;
} 

NOTE:

std::move doesn't move anything. It just casts from lvalue reference to rvalue reference and your returned object could have been constructed via a move constructor (and copy elision could be suppressed) but the compiler doesn't implicitly-declared move constructor because you have defined the destructor (& I have added copy constructor in my example)

Outputs:

Constructor! # 1
Copy Constructors # 2
Destructor! # 3
Copy Constructors # 4
Destructor! # 5
Destructor! # 6

See my comments below:

  1. The object bla is constructed in function GetBla() via default constructor.
  2. The return value of GetBla() function is copy-constructed from the object created in # 1.
  3. bla object (constructed in #1) gets destroyed and it's destructor is called.
  4. std::make_unique<Bla> calls new and then calls the appropriate constructor and it chooses the copy constructor.
  5. The object created in #2 are destroyed.
  6. Finally, the object created in #4 is destroyed.

Upvotes: 2

Igor R.
Igor R.

Reputation: 15075

The right way to create unique_ptr:

auto bla = std::make_unique<Bla>();

However, your code creates 3 instances of Bla:

  1. Local object bla in GetBla() function.
  2. Return value of GetBla().
  3. Finally, make_unique() creates one more instance.

NOTE:

  1. In presence of user-defined destructor, the compiler does not generate move-constructor, so GetBla() return value is a copy of the local object bla.
  2. Since GetBla() returns move'ed local object, copy-elision is suppressed.

Upvotes: 2

rustyx
rustyx

Reputation: 85286

There are indeed 3 times that an instance of Bla is constructed.

Bla GetBla() {
  Bla bla;    // 1st construction
  return std::move(bla); // 2nd construction (return by copy)
}

Don't return by move. Just return bla, in most cases the copy will be elided.

  auto bla = std::make_unique<Bla>(GetBla());  // 3rd construction - Bla copy construction

Note that make_unique<Bla> always constructs a new instance. In this case because you're passing another instance, it becomes copy-construction.

A hint that copy construction takes place is that your default constructor is invoked only once, while the destructor is invoked 3 times. That's because in the other 2 cases the implicit copy (or move) constructor is invoked (Bla::Bla(Bla const&)).

Upvotes: 13

Lukas-T
Lukas-T

Reputation: 11340

The compiler may even warn you that

moving a local object in a return statement prevents copy elision.

I'm not 100% sure, but I think you get the three desctructor calls from:

  • The local variable bla from GetBla()
  • The return value from GetBla() after it was used in std::make_unique<Bla>(GetBla());
  • Obviously from the destructor of the std::unique_ptr

The easiest way is to let std::make_uniqe invoke the default constructor of Bla:

auto bla = std::make_unique<Bla>(); // Calls Bla::Bla() to initalize the owned object
#include <iostream>
#include <memory>

class Bla {
  public:
      Bla() { std::cout << "Constructor!\n"; }
      ~Bla() { std::cout << "Destructor!\n"; }
};

int main() {
  auto bla = std::make_unique<Bla>();
}

Output

Constructor!
Destructor!

Upvotes: 3

Related Questions