【发布时间】:2020-03-06 16:49:55
【问题描述】:
当我尝试通过静态函数调用create_instance() 在堆上构造一个对象说LeakySingleton,然后尝试通过delete 操作。
据我了解,考虑到下面的源代码列表,main() 内的变量leaky_singleton 指向create_instance() 返回的堆分配资源。因此,我们通过create_instance 函数间接在堆上分配了一个对象LeakySingleton。
现在,如果我在leaky_singleton 上显式调用delete 运算符或delete 函数,那么它首先调用析构函数并检查它是否满足instance != nullptr 条件,然后删除instance 指向的对象应该是删除。
如果这个对象LeakySingleton::instance被删除了,那么dtor就没有理由再次调用自己,还是我在这里遗漏了什么?
使用和不使用 valgrind 调用它会导致分段错误(由于堆栈溢出导致的无效内存访问):
Segmentation fault (core dumped)
单步调试器会导致无休止的析构函数调用(堆栈溢出的罪魁祸首)。
来自 cplusplus.com (http://www.cplusplus.com/forum/general/40044/):
如果您删除您的对象,它会尝试删除自己,这将 导致它尝试删除自己,这将导致它删除 本身,它将...
当我简单地使用delete 运算符/函数来释放静态类成员变量LeakySingleton::instance 指向的堆对象LeakySingleton 时,为什么它会尝试删除自己?
堆分配的资源由指向LeakySingleton 对象的LeakySingleton::instance 指针变量指向。那么为什么显式的delete 函数调用不会删除或释放分配的堆对象,而是无休止地递归呢?我在这里错过了什么?
(我目前对 dtor 和 ctor 的理解:new 函数/运算符为堆上的对象分配内存并调用构造函数,delete 函数调用析构函数,在我的情况下还调用 delete里面的操作符/函数。)
来源:
main.cpp:
class Singleton final
{
public:
static Singleton & create_instance(int);
~Singleton() = default;
private:
int x;
Singleton(int);
Singleton(Singleton &) = delete;
Singleton(Singleton &&) = delete;
Singleton & operator=(Singleton &) = delete;
Singleton & operator=(Singleton &&) = delete;
};
Singleton::Singleton(int t_x) : x{t_x}
{}
Singleton & Singleton::create_instance(int t_x)
{
static Singleton instance{t_x};
return instance;
}
// Potential endless dtor calls inside:
class LeakySingleton final
{
public:
static LeakySingleton * create_instance(int);
~LeakySingleton();
private:
int x;
static LeakySingleton * instance;
LeakySingleton(int);
LeakySingleton(LeakySingleton &) = delete;
LeakySingleton(LeakySingleton &&) = delete;
LeakySingleton & operator=(LeakySingleton &) = delete;
LeakySingleton & operator=(LeakySingleton &&) = delete;
};
LeakySingleton * LeakySingleton::instance = nullptr;
LeakySingleton::LeakySingleton(int t_x) : x{t_x}
{}
LeakySingleton::~LeakySingleton()
{
if (instance != nullptr)
{
delete instance;
instance = nullptr;
}
}
LeakySingleton * LeakySingleton::create_instance(int t_x)
{
if (instance == nullptr)
{
instance = new LeakySingleton{t_x};
}
return instance;
}
int main()
{
// The correct implementation with no issues:
{
Singleton & singleton = Singleton::create_instance(42);
}
// The faulty implementation causing the dtor to recurse endlessly and resulting in a segfault:
{
LeakySingleton * leaky_singleton = LeakySingleton::create_instance(42);
delete leaky_singleton;
}
return 0;
}
生成文件:
CC = g++
CFLAGS = -g -Wall -Wextra -pedantic -std=c++11
SRC = main.cpp
TARGET = app
RM = rm -rf
.PHONY: all clean
all: $(TARGET)
clean:
$(RM) $(TARGET)
$(TARGET): $(SRC)
$(CC) $(CFLAGS) $^ -o $@
【问题讨论】:
-
delete 调用 dtor,它调用 delete,它 clls dtor,... 在 delete 返回之前,您不会将 instance 设置为 nullptr(它永远不会)。
-
@stark,感谢您的输入,但我仍然对为什么会这样感到困惑。当 dtor 被调用时,delete 发出到目前为止这么好,但是为什么 delete 会导致另一个 dtor 调用呢?
-
当你调用new时,它会分配内存,然后调用ctor。当你调用 delete 时,它首先调用 dtor,然后释放内存。
-
是的,到目前为止我可以关注你,但是为什么它会被多次调用?如果您考虑这个简单的源代码列表,那么 dtor 只会被调用一次:privatebin.net/…
-
实例 == 这里。您首先调用 delete (调用析构函数),然后使指针无效 - 所以在每次调用时,指针都不为 null [还]。另外,您是否有理由不使用带有函数级静态变量的典型单例实现?
标签: c++ design-patterns singleton destructor self-destruction