单例:析构函数怎么会被调用两次?
Singleton: how can destructor be called twice?
几分钟前我问了一个 关于单例实现的问题,我从@LightnessRacesinOrbit 那里得到了很好的回答。
但是我无法理解为什么在下一个示例中如果我在变量 inst
中实例化 Singleton
它的析构函数调用了两次?
#include <iostream>
class Singleton
{
public:
~Singleton() { std::cout << "destruction!\n"; }
static Singleton& getInstance()
{
static Singleton instance;
return instance;
}
void foo() { std::cout << "foo!\n"; }
private:
Singleton() { std::cout << "construction!\n"; }
};
int main()
{
Singleton inst = Singleton::getInstance();
inst.foo();
}
输出:
construction!
foo!
destruction!
destruction!
更准确地说,我理解为什么它被调用了两次。但是我不明白如果在第一个析构函数之后 class 的实例被销毁,它怎么能被调用两次?为什么没有例外?
或者它没有被摧毁?为什么?
这一行
Singleton inst = Singleton::getInstance();
应该是
Singleton& inst = Singleton::getInstance();
那么你只会看到一个析构函数调用。
这样写,Singleton::getInstance()
returns 参考,然后复制 到inst
。因此,从您的函数返回的 Singleton
和 副本都被销毁了。你从来没有看到副本被构造,因为没有使用默认构造函数,而是复制构造函数。
在第二种方法中,引用被返回,然后你只需要 inst
成为那个 Singleton
的引用而不是复制。
正如其他人所提到的,您可以使 class 不可复制且不可移动以防止这种情况发生
Singleton(Singleton const&) = delete; // Copy construct
Singleton(Singleton&&) = delete; // Move construct
Singleton& operator=(Singleton const&) = delete; // Copy assign
Singleton& operator=(Singleton &&) = delete; // Move assign
行
Singleton inst = Singleton::getInstance();
使用自动生成的复制构造函数复制您的实例。为了防止这种情况发生,添加
Singleton( const Singleton& ) = delete;
到您的 class 以防止那些意外复制。为确保捕获更隐蔽的错误,还添加
void operator=( const Singleton& ) = delete;
还有。您不必显式删除移动构造或赋值,因为编译器不会在声明其他(已删除)成员的情况下生成它们。
尝试添加一个public复制构造函数:
Singleton(const Singleton&) { std::cout << "copy construction!\n"; }
您的输出将变为:
construction!
copy construction!
foo!
destruction!
destruction!
有两个 "singletons" 还活着,因为来自 getInstance()
的那个被复制了。为避免无意中复制单例,您应该删除复制构造函数和赋值运算符:
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
您可以将单例隐藏在常规 class 中,而不展示其静态性质:
#include <iostream>
#include <string>
class Singleton // A bad name for a specific class, you should not generalize
{
private:
struct Context {
std::string name;
Context()
: name("Hello")
{}
};
private:
static Context& context() { static Context result; return result; }
public:
const std::string& name() const { return context().name; }
};
int main()
{
Singleton a;
std::cout << a.name() << '\n';
}
几分钟前我问了一个
但是我无法理解为什么在下一个示例中如果我在变量 inst
中实例化 Singleton
它的析构函数调用了两次?
#include <iostream>
class Singleton
{
public:
~Singleton() { std::cout << "destruction!\n"; }
static Singleton& getInstance()
{
static Singleton instance;
return instance;
}
void foo() { std::cout << "foo!\n"; }
private:
Singleton() { std::cout << "construction!\n"; }
};
int main()
{
Singleton inst = Singleton::getInstance();
inst.foo();
}
输出:
construction!
foo!
destruction!
destruction!
更准确地说,我理解为什么它被调用了两次。但是我不明白如果在第一个析构函数之后 class 的实例被销毁,它怎么能被调用两次?为什么没有例外?
或者它没有被摧毁?为什么?
这一行
Singleton inst = Singleton::getInstance();
应该是
Singleton& inst = Singleton::getInstance();
那么你只会看到一个析构函数调用。
这样写,Singleton::getInstance()
returns 参考,然后复制 到inst
。因此,从您的函数返回的 Singleton
和 副本都被销毁了。你从来没有看到副本被构造,因为没有使用默认构造函数,而是复制构造函数。
在第二种方法中,引用被返回,然后你只需要 inst
成为那个 Singleton
的引用而不是复制。
正如其他人所提到的,您可以使 class 不可复制且不可移动以防止这种情况发生
Singleton(Singleton const&) = delete; // Copy construct
Singleton(Singleton&&) = delete; // Move construct
Singleton& operator=(Singleton const&) = delete; // Copy assign
Singleton& operator=(Singleton &&) = delete; // Move assign
行
Singleton inst = Singleton::getInstance();
使用自动生成的复制构造函数复制您的实例。为了防止这种情况发生,添加
Singleton( const Singleton& ) = delete;
到您的 class 以防止那些意外复制。为确保捕获更隐蔽的错误,还添加
void operator=( const Singleton& ) = delete;
还有。您不必显式删除移动构造或赋值,因为编译器不会在声明其他(已删除)成员的情况下生成它们。
尝试添加一个public复制构造函数:
Singleton(const Singleton&) { std::cout << "copy construction!\n"; }
您的输出将变为:
construction!
copy construction!
foo!
destruction!
destruction!
有两个 "singletons" 还活着,因为来自 getInstance()
的那个被复制了。为避免无意中复制单例,您应该删除复制构造函数和赋值运算符:
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
您可以将单例隐藏在常规 class 中,而不展示其静态性质:
#include <iostream>
#include <string>
class Singleton // A bad name for a specific class, you should not generalize
{
private:
struct Context {
std::string name;
Context()
: name("Hello")
{}
};
private:
static Context& context() { static Context result; return result; }
public:
const std::string& name() const { return context().name; }
};
int main()
{
Singleton a;
std::cout << a.name() << '\n';
}