使用私有析构函数删除对象

10

在下面的代码中,怎么可能允许删除拥有私有析构函数的对象?我将真实程序简化为以下示例,但它仍然可以编译并正常工作。

class SomeClass;

int main(int argc, char *argv[])
{
  SomeClass* boo = 0; // in real program it will be valid pointer
  delete boo; // how it can work?

  return -1;
}

class SomeClass
{
private:
  ~SomeClass() {}; // ! private destructor !
};

有趣的是,如果我将类的定义移到main()之前,那么就会出现编译错误。否则,我只会得到一个“警告 C4150: 删除指向不完全类型 'SomeClass' 的指针;未调用析构函数”。 - Naveen
1
@Naveen:这是可以预料的。不完整类型是一个问题。私有析构函数是另一个问题。每个都有自己的诊断消息。您可以通过移动SomeClass的定义来在两者之间切换。 - AnT stands with Russia
3个回答

15
你正在尝试删除不完整类型的对象。C++标准规定,在这种情况下,您将获得未定义的行为(5.3.5/5):
如果在删除点处被删除的对象具有不完整的类类型,并且完整的类具有非平凡的析构函数或释放函数,则行为是未定义的。
为了检测这种情况,您可以使用boost::checked_delete:
template<typename T> 
inline void checked_delete( T* p )
{
    typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];
    (void) sizeof(type_must_be_complete);
    delete p;
}

1
这利用了sizeof()即使对于空的structs/classes也不会返回0的特性,这是我忘记的。ISO-IEC-14882第9.1节:“完整的类类型对象和成员子对象应具有非零大小。” 这意味着数组中的每个对象都将具有唯一的内存地址。结构体也应该是同样的情况吧——在C语言中是否也是如此? - leander
@leander:并不完全是这样。你看,在C++中,对于不完整的类型应用 sizeof非法的。这就是上面的代码尝试通过 (void) sizeof ... 部分捕获的内容。这个表达式将在遵守标准的编译器上通过错误停止编译。然而,如果一些奇怪的编译器允许在不完整的类型上使用 sizeof(作为扩展)并返回0,则 typedef ... 部分将捕获这些并强制执行“负大小的数组”失败。因此,你在这里有一个主要的“陷阱”((void) 部分)和一个辅助的“备用陷阱”(typedef 部分)。 - AnT stands with Russia
@AndreyT,我说的是空结构体,而不是不完整类型。Leander问:结构体的sizeof是否为非零值。C标准没有保证,但C++标准有。 - Kirill V. Lyadvinsky
2
@Kirill "struct X {}; "是一个空结构体,它会在任何符合C标准的编译器上引起编译错误(或更正式地说,“诊断消息”)。因为,如我上面所说,C不允许空结构体。 - AnT stands with Russia
我现在明白了,我错过了struct X{};在标准C中是无效的。我已经在gcc中编译它,它没有警告我有错误。 - Kirill V. Lyadvinsky
显示剩余5条评论

8

这段代码会导致未定义行为(UB)。在C++中,删除一个具有非平凡析构函数的不完整类型对象是UB。而在你的代码中,类型 SomeClassdelete 点处是不完整的,并且它有一个非平凡的析构函数。编译器通常会发出警告,因为在C++中,这实际上并不违反约束。

所以严格来说,你的代码并没有“正常工作”。它只是编译通过了,在运行时做了一些未定义的事情。

编译器并不需要捕捉此错误。原因是如果您的对象具有平凡析构函数,则可能完全正常。编译器无法知道这种类型最终将具有何种析构函数,因此无法确定这是否是错误。


如果 (1) T 有一个平凡的析构函数 (2) T 没有重新定义 delete operator,那么就可以了。 - curiousguy

4

由于在调用operator delete时,SomeClass类型没有完全声明。

删除这样的指针是未定义行为,但实际上大多数编译器只会释放内存(如果指针非空)而不调用析构函数。

例如,g++会警告您此问题:

foo.cpp: In function 'int main(int, char**)':
foo.cpp:6: warning: possible problem detected in invocation of delete operator:
foo.cpp:5: warning: 'boo' has incomplete type
foo.cpp:1: warning: forward declaration of 'struct SomeClass'
foo.cpp:6: note: neither the destructor nor the class-specific operator delete will be called, even if they are declared when the class is defined.

1
删除这样的指针只会释放内存... - 不!行为是未定义的,即任何关于“本质上”发生的假设都是无效的。 - DevSolar
1
@DevSolar:确实,根据标准它是未定义的。实际上,大多数编译器都会发生这种情况。我更新了答案以反映这一点。 - laalto
我在依赖于行为良好的范围之外的任何东西方面有些问题。尤其是在同事的代码中。这让我感到非常烦恼。我归咎于像你这样的答案。(;-) <<--重要的笑脸) - DevSolar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接