模板化的指针类可以有虚拟析构函数吗?

Posted

技术标签:

【中文标题】模板化的指针类可以有虚拟析构函数吗?【英文标题】:Can a templated Pointer class have a virtual destructor? 【发布时间】:2011-06-28 17:02:51 【问题描述】:

在使用自制指针类实现 pimpl 习语时,我遇到了一个令人惊讶的发现(我知道:为什么要自己动手?但请耐心等待)。以下三个文件包含一个最小示例:

指针.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

可以通过从指针析构函数中删除关键字 virtual 来避免警告。

这对我来说毫无意义。这个警告是合法的,还是 MSVC 编译器中的错误?如果是这样,我可以放心地忽略该警告吗?

我知道在这种情况下将析构函数设为虚拟是没有意义的,但请记住,这只是一个最小的可编译示例。我的原始代码要复杂得多。

【问题讨论】:

当您写 bear with me 时,我认为滚动您自己的智能指针的原因将在您的帖子中稍后介绍。可悲的是它不是。所以,我不得不问 - 为什么 您是否查看过 std::auto_ptr (cplusplus.com/reference/std/memory/auto_ptr) 或 Boost Smart 指针 (boost.org/doc/libs/1_46_1/libs/smart_ptr/smart_ptr.htm)? Foo.cpp 的内部结构在这里非常重要...您实际上有明确的~Foo(),还是使用提供的编译器?你不需要做任何事情,一个空的 body 就可以了,只要在你写之前 FooPrivate 已经在 Foo.cpp 中完全定义好了。 @Dennis: ~Foo() 在类定义中声明。所以它是用户定义的(或者根本没有定义,在这种情况下,一段时间后应该会导致链接错误)。 Foo.cpp 的内容与main.cpp 的编译无关,因为它们是不同的翻译单元,这是编译时而不是链接时警告。 @Steve:我实际上并不是要发布该评论,而是选择创建一个正确的答案。然而,理论上,用户定义的析构函数可能发生在 FooPrivate... 的定义之前,但由于警告在没有 virtual 的情况下消失,因此几乎可以肯定情况并非如此。 【参考方案1】:

没有virtual,只有一个地方会调用析构函数;在~Foo 内,此时您可能已经完全定义了FooPrivate。如果在其他地方创建了另一个 Pointer&lt;FooPrivate&gt; 实例,您可能会收到警告,但由于您不这样做,编译器可以告诉您行为安全。

使用virtual,理论上您可以从Pointer&lt;FooPrivate&gt; 派生,并且可以从FooPrivate 未完全定义的某个地方销毁新对象。编译器不肯定你不这样做,所以它会发出警告。在这种微不足道的情况下,您可以放心地忽略它,但如果您确实需要虚拟析构函数,那么将其铭记于心可能是个好主意。

【讨论】:

第二段是什么意思?请提供一个具体的例子。 @Alf:老实说,在任何情况下我都无法让 VS2010 发出警告,而且我无法访问 VS2008。重要的是验证一个析构函数在哪里被多态调用比验证一个析构函数在哪里被静态调用更困难。使用非虚拟析构函数,所有破坏都必须静态完成。一旦引入了虚拟析构函数,编译器显然会选择谨慎行事。这在任何情况下都可能是误报,我不确定。 我没有问题让 VS2010 发出警告(我没有使用任何特殊选项),我仍然不明白你的第二段。我怀疑这段代码没有意义。一个具体的例子会很好。干杯,【参考方案2】:

由于您正在为类 Foo 提供析构函数,因此警告似乎完全不正确和虚假。

只是为了检查我是否在文件 [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:: Foo:: Foo:: FooPrivate::

显然,可执行文件并没有按照愚蠢的警告所说的那样做……

干杯,

【讨论】:

我现在意识到我应该发布 Foo.cpp 的详细信息。它看起来和你的一模一样。请注意,它编译时没有警告。发出警告的是 main.cpp。【参考方案3】:

在不合格的类型上调用 delete 是未定义的行为。

【讨论】:

你怎么知道Foo::~Foo的网站不完整? 仅当不完整类型的析构函数一旦完成,结果证明是非平凡的。【参考方案4】:

因为您还没有给出FooPrivate 的完整定义,所以编译器不知道它的vtable 是什么样的。由于它不能调用它无法定位的虚函数,所以它放弃了。

【讨论】:

实际上它并没有保释。它只是发出虚假的愚蠢警告。然后继续发出正确的代码...

以上是关于模板化的指针类可以有虚拟析构函数吗?的主要内容,如果未能解决你的问题,请参考以下文章

使用非虚拟析构函数有啥具体原因吗?

C++ 虚拟析构函数 (virtual destructor)

每个类都应该有一个虚拟析构函数吗?

当类没有析构函数时,智能指针或作用域指针会删除对象吗

删除指向子类的指针会调用基类析构函数吗?

具有虚拟析构函数的基类子类中的默认析构函数