#include <memory>
#include <iostream>
class Token { public:
Token() { std::cout << "Token()"; }
~Token() { std::cout << "~Token()"; }
};
template <class T>
std::unique_ptr<T> foo(T t0) {
return std::unique_ptr<T>(new T(t0)); };
int main() {
Token&& t = Token();
auto ptr = foo<Token>(t);
return 0;
}
On which occasions will the destructor be called?
I think it will be called firstly when we call Token()
, it creates a temporary object which is destructed immediately, then in the foo()
function when t0
is destructed, and then when main()
ends, and ptr
goes out of scope.
But my friend says otherwise, so how will it actually be?
CodePudding user response:
When a scope ends and the automatic objects of that scope are destroyed, their destructors will be called in the reverse order the objects were created:
int main() {
Token&& t = Token(); // Token1 constructed
// lifetime of Token1 extended because it was bound to t
auto ptr = foo<Token>(t); // creates a copy of Token1 for the argument of foo: Token2
// Token3 constructed by foo in dynamic memory
// and bound to ptr, which
// resides in automatic memory
// Token2 (temporary copy) is automatically destroyed
return 0;
// Last automatic object is destroyed: ptr
// thus, uniqe_ptr destroys Token3
// t is destroyed. This destroys Token1 because
// its lifetime-extending reference
// went out of scope
}
Demo
If you modify your Token class slightly you can observe this live:
class Token {
inline static int C = 1;
int c = C ;
public:
Token(Token const&) : Token() {}
Token(Token&&) : Token() {}
Token() { std::cout << "Token(" << c << ")\n"; }
~Token() { std::cout << "~Token(" << c << ")\n"; }
};
Output:
Token(1)
Token(2)
Token(3)
~Token(2)
~Token(3)
~Token(1)
CodePudding user response:
@bitmask's explanation about life-time is pretty clear. Besides, if you want to get the best performance, maybe you could implement the move constructor for your class Token
, and then:
template <class T>
std::unique_ptr<T> foo(T& t0) { // pass-by-reference
return std::make_unique<T>(std::move(t0));
};
As you have used rvalue reference Token&& t
to extend the temporary Token
object's lifetime in function int main()
and the rvalue reference t
is a lvalue, so auto foo(T &)
will deduce T
as class Token
and accept t
as its parameter t0
according to Overload resolution. Furthermore, std::move
cast lvalue t0
to rvalue reference and std::make_unique
call the constructor which satisfies std::is_nothrow_constructable_v<Token,Token&&>
is true
, the move constructor and copy constructor are both candicate functions. If you have implemented Token(Token&&)
, move constructor will be called. Otherwise is copy constructor.