当用自制指针类实现pimpl成语时,我遇到了一个令人惊讶的启示(我知道:为什么要自己滚动?但请耐心等待)。以下三个文件包含一个最小示例:
Pointer.h:
#pragma once
template <typename T>
class Pointer
{
public:
Pointer(T*p=0)
: _p(p)
{
}
virtual ~Pointer()
{
delete _p;
}
private:
void operator=(const Pointer&);
Pointer(const Pointer&);
private:
T*_p;
};
foo.h中:
#pragma once
#include "Pointer.h"
struct Foo
{
Foo();
~Foo();
private:
void operator=(const Foo&);
Foo(const Foo&);
private:
Pointer<struct FooPrivate> p;
};
main.cpp中:
#include "Foo.h"
int main(int argc, char* argv[])
{
Foo foo;
return 0;
}
不要介意Foo.cpp
的内部结构。当我使用MSVC 2008编译main.cpp
时,我收到警告:
pointer.h(13) : warning C4150: deletion of pointer to incomplete type 'FooPrivate'; no destructor called
可以通过从Pointers析构函数中删除关键字virtual来避免警告。
这对我没有意义。这个警告是合法的,还是MSVC编译器中的错误?如果是这样,我可以安全地忽略警告吗?
我知道在这种情况下使析构函数虚拟是没有意义的,但请记住,这只是一个最小的可编译示例。我原来的代码要复杂得多。
答案 0 :(得分:4)
没有virtual
,析构函数只会被调用一个地方;在~Foo
范围内,此时您可能已完全定义FooPrivate
。如果在其他地方创建了另一个Pointer<FooPrivate>
实例,您可能会收到警告,但由于您没有,编译器可以告诉您安全行为。
使用virtual
,理论上可以从Pointer<FooPrivate>
派生,并且可以从FooPrivate
未完全定义的某个地方销毁新对象。编译器不肯定你不这样做,所以它会发出警告。在这个简单的案例中你可以放心地忽略它,但如果你真的需要一个虚拟的析构函数,那么把它放在心上可能是个好主意。
答案 1 :(得分:2)
由于您要为班级Foo
提供析构函数,因此警告似乎完全不正确&amp;杂散。
只是检查我在文件[foo.cpp]中添加了这段代码:
#include "foo.h"
#include <iostream>
using namespace std;
struct FooPrivate
{
FooPrivate() { cout << "FooPrivate::<init>" << endl; }
~FooPrivate() { cout << "FooPrivate::<destroy>" << endl; }
};
Foo::Foo()
: p( new FooPrivate )
{
cout << "Foo::<init>" << endl;
}
Foo::~Foo()
{
cout << "Foo::<destroy>" << endl;
}
它产生了与您相同的警告(使用Visual C ++ 10.0),但输出
FooPrivate ::&LT; INIT&GT;
FOO ::&LT; INIT&GT;
FOO ::&LT;破坏&GT;
FooPrivate ::&LT;破坏&GT;
显然,可执行文件没有做傻警告说的那样......
干杯&amp;第h。,
答案 2 :(得分:0)
对不完整类型调用delete
是未定义行为。
答案 3 :(得分:0)
因为您没有给出FooPrivate
的完整定义,所以编译器不知道它的vtable是什么样的。由于无法调用无法定位的虚函数,因此它会失败。