1
#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?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
dajde
  • 13
  • 3
  • ⟼Remember, it's always important, *especially* when learning and asking questions on Stack Overflow, to keep your code as organized as possible. [Consistent indentation](https://en.wikipedia.org/wiki/Indentation_style) helps communicate structure and, importantly, intent, which helps us navigate quickly to the root of the problem without spending a lot of time trying to decode what's going on. – tadman Dec 16 '22 at 14:50
  • 5
    `t` extends the lifetime of the temporary `Token()` until the reference goes out of scope, so it should be the last one to be destroyed. – François Andrieux Dec 16 '22 at 14:53
  • 2
    @drescherjm The code -- as posted -- doesn't help you figure out which destructor call belongs to which object. Furthermore, you see more destructor calls than constructors because two of the constructions are copy/move constructions (one implicit, one explicit) which fail to print out any diagnostics. – bitmask Dec 16 '22 at 15:19

2 Answers2

2

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)

(live demo)

bitmask
  • 32,434
  • 14
  • 99
  • 159
0

@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.

Arlen LT
  • 11
  • 2