#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;
}
В каких случаях будет вызываться деструктор?
Я думаю, что он будет вызываться первым, когда мы вызываем Token()
, он создает временный объект, который немедленно уничтожается, затем в функции foo()
, когда t0
уничтожается, а затем, когда main()
заканчивается, и ptr
выходит из области видимости.
Но мой друг говорит иначе, так как же это будет на самом деле?
t
продлевает время жизни временного Token()
до тех пор, пока ссылка не выйдет за пределы области действия, поэтому она должна быть уничтожена последней.
@drescherjm Код в опубликованном виде не поможет вам выяснить, какой вызов деструктора принадлежит какому объекту. Кроме того, вы видите больше вызовов деструкторов, чем конструкторов, потому что две из этих конструкций являются конструкциями копирования/перемещения (одна неявная, одна явная), которые не могут распечатать какую-либо диагностику.
Когда область действия заканчивается и автоматические объекты этой области уничтожаются, их деструкторы будут вызываться в порядке, обратном порядку создания объектов:
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
}
Если вы немного измените свой класс Token, вы можете наблюдать это вживую:
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"; }
};
Выход:
Token(1)
Token(2)
Token(3)
~Token(2)
~Token(3)
~Token(1)
Объяснение @bitmask о времени жизни довольно ясно. Кроме того, если вы хотите получить максимальную производительность, возможно, вы могли бы реализовать конструктор перемещения для своего class Token
, а затем:
template <class T>
std::unique_ptr<T> foo(T& t0) { // pass-by-reference
return std::make_unique<T>(std::move(t0));
};
Поскольку вы использовали ссылку rvalue Token&& t
для продления времени жизни временного Token
объекта в функции int main()
, а ссылка rvalue t
является lvalue, поэтому auto foo(T &)
выведет T
как class Token
и примет t
в качестве своего параметра t0
в соответствии с разрешением перегрузки. Кроме того, std::move
приводит lvalue t0
к ссылке rvalue и std::make_unique
вызывает конструктор, который удовлетворяет std::is_nothrow_constructable_v<Token,Token&&>
, является true
, конструктор перемещения и конструктор копирования являются функциями-кандидатами. Если вы реализовали Token(Token&&)
, будет вызван конструктор перемещения. В противном случае это конструктор копирования.
⟼Помните, что всегда важно, особенно когда вы изучаете и задаете вопросы о Stack Overflow, поддерживать как можно более организованный код. Постоянный отступ помогает передать структуру и, что важно, намерение, что помогает нам быстро перейти к корню проблемы, не тратя много времени на попытки расшифровать, что происходит.