Reputation: 183
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
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:
bla
is constructed in function GetBla()
via default constructor.GetBla()
function is copy-constructed from the object created in # 1
.bla
object (constructed in #1) gets destroyed and it's destructor is called. std::make_unique<Bla>
calls new
and then calls the appropriate constructor and it chooses the copy
constructor.Upvotes: 2
Reputation: 15075
The right way to create unique_ptr
:
auto bla = std::make_unique<Bla>();
However, your code creates 3 instances of Bla
:
bla
in GetBla()
function.GetBla()
.make_unique()
creates one more instance.NOTE:
GetBla()
return value is a copy of the local object bla
.GetBla()
returns move
'ed local object, copy-elision is suppressed.Upvotes: 2
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
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:
bla
from GetBla()
GetBla()
after it was used in std::make_unique<Bla>(GetBla());
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