我在检查内存泄漏后在我的应用程序中遇到了这个问题,并发现我的一些类没有被销毁。
下面的代码分为3个文件,它应该实现一个名为pimpl的模式。预期的方案是让Cimpl
构造函数和析构函数都打印它们的消息。但是,这不是我用g ++得到的。在我的应用程序中,只有构造函数被调用。
classes.h:
#include <memory>
class Cimpl;
class Cpimpl {
std::auto_ptr<Cimpl> impl;
public:
Cpimpl();
};
classes.cpp:
#include "classes.h"
#include <stdio.h>
class Cimpl {
public:
Cimpl() {
printf("Cimpl::Cimpl()\n");
}
~Cimpl() {
printf("Cimpl::~Cimpl()\n");
}
};
Cpimpl::Cpimpl() {
this->impl.reset(new Cimpl);
}
main.cpp中:
#include "classes.h"
int main() {
Cpimpl c;
return 0;
}
以下是我能够进一步发现的内容:
g++ -Wall -c main.cpp
g++ -Wall -c classes.cpp
g++ -Wall main.o classes.o -o app_bug
g++ -Wall classes.o main.o -o app_ok
看起来像是在两种可能的情况之一中调用析构函数,它取决于链接顺序。使用app_ok,我能够获得正确的场景,而app_bug的行为与我的应用程序完全相同。
在这种情况下我是否缺少任何智慧? 感谢您的任何建议!
答案 0 :(得分:1)
pimpl习惯用法的目标是不必在头文件中公开实现类的定义。但是所有标准智能指针都需要将其模板参数的定义在声明处显示才能正常工作。
这意味着这是您真正想要使用new
,delete
和裸指针的极少数情况之一。 (如果我对此错了,并且有一个可用于pimpl的标准智能指针,请有人告诉我。)
struct Cimpl;
struct Cpimpl
{
Cpimpl();
~Cpimpl();
// other public methods here
private:
Cimpl *ptr;
// Cpimpl must be uncopyable or else make these copy the Cimpl
Cpimpl(const Cpimpl&);
Cpimpl& operator=(const Cpimpl&);
};
#include <stdio.h>
struct Cimpl
{
Cimpl()
{
puts("Cimpl::Cimpl()");
}
~Cimpl()
{
puts("Cimpl::~Cimpl()");
}
// etc
};
Cpimpl::Cpimpl() : ptr(new Cimpl) {}
Cpimpl::~Cpimpl() { delete ptr; }
// etc
答案 1 :(得分:1)
问题是,在定义auto_ptr<Cimpl>
对象时,Cimpl
是一个不完整的类型,也就是说,编译器只看到Cimpl
的前向声明。没关系,但是因为它最终会删除它持有指针的对象,所以你必须遵守这个要求,来自[expr.delete] / 5:
如果要删除的对象的类型不完整 删除和完整的类有一个非平凡的析构函数或 解除分配函数,行为未定义。
所以这段代码会遇到未定义的行为,所有的赌注都会关闭。
答案 2 :(得分:0)
该代码违反了One Definition Rule。在classes.h中定义了类Cimpl
,在classes.cpp文件中定义了类Cimpl
。结果是未定义的行为。
答案 3 :(得分:0)
为了清晰而编辑,原文保留在下面。
此代码具有未定义的行为,因为在main.cpp
的上下文中,隐式Cpimpl::~Cpimpl
析构函数只有Cimpl
的前向声明,但auto_ptr
(或任何其他形式)做delete
)需要一个完整的定义来合法地清理Cimpl
。鉴于它是未定义的行为,不需要进一步解释您的观察结果。
原始答案:
我怀疑这里发生的事情是Cpimpl
的隐式析构函数是在classes.h
和而不是的上下文中生成的,可以访问{{的完整定义1}}。然后,当Cimpl
尝试执行其操作并清理其包含的指针时,它会删除一个不完整的类,这是未定义的行为。鉴于它未定义,我们不必再进一步解释它根据链接顺序以不同方式工作是完全可以接受的。
我怀疑auto_ptr
的显式析构函数在源文件中有定义可以解决您的问题。
编辑:实际上现在我再看一遍,我相信你的程序违反了一个定义规则。在Cpimpl
中,它看到一个隐式的析构函数,它不知道如何调用main.cpp
的析构函数(因为它只有一个前向声明)。在Cimpl
中,隐式析构函数 可以访问classes.cpp
的定义,从而如何调用它的析构函数。