析构函数中奇怪的枚举

zan*_*ngw 83 c++ enums

目前,我正在阅读源代码Protocol Buffer,我发现这里enum定义一个奇怪的代码

  ~scoped_ptr() {
    enum { type_must_be_complete = sizeof(C) };
    delete ptr_;
  }

  void reset(C* p = NULL) {
    if (p != ptr_) {
      enum { type_must_be_complete = sizeof(C) };
      delete ptr_;
      ptr_ = p;
    }
  }
Run Code Online (Sandbox Code Playgroud)

为什么enum { type_must_be_complete = sizeof(C) };定义在这里?它是干什么用的?

Moh*_*ain 81

这个技巧通过确保在编译这个析构函数时可以使用C的定义来避免UB.否则编译将失败,因为无法确定sizeof不完整类型(前向声明的类型),但可以使用指针.

在编译的二进制文件中,此代码将被优化并且不会产生任何影响.

请注意:从5.3.5/5中删除不完整类型可能是未定义的行为:

如果被删除的对象在删除时具有不完整的类类型,并且完整的类具有非平凡的析构函数或释放函数,则行为是未定义的.

g++ 甚至发出以下警告:

警告:在调用delete操作符时检测到可能的问题:
警告:'p'具有不完整类型
警告:'struct C'的前向声明


Bat*_*eba 31

sizeof(C)如果不是完整类型,将在编译时失败C.将本地范围设置enum为它使语句在运行时是良性的.

这是程序员保护自己的一种方式:delete ptr_如果它具有非平凡的析构函数,则不完整类型的后续行为是未定义的.


Che*_*Alf 28

要理解它enum,首先考虑没有它的析构函数:

~scoped_ptr() {
    delete ptr_;
}
Run Code Online (Sandbox Code Playgroud)

这里ptr_是一个C*.如果此时类型C不完整,即编译器知道的所有内容struct C;,则(1)默认生成的 do-nothing析构函数用于指向的C实例.对于由智能指针管理的对象,这不太可能是正确的做法.

如果通过指向不完整类型的指针进行删除始终具有未定义行为,则标准可能只需要编译器对其进行诊断并失败.但是当真正的析构函数是微不足道的时候它是明确定义的:程序员可以拥有的知识,但编译器没有.为什么语言定义并允许这超出我的范围,但C++支持许多当前不被视为最佳实践的实践.

完整类型具有已知大小,因此,sizeof(C)当且仅当C它是完整类型时才会编译- 具有已知的析构函数.所以它可以用作守卫.一种方法很简单

(void) sizeof(C);  // Type must be complete
Run Code Online (Sandbox Code Playgroud)

猜想有了一些编译器和选项,编译器会在它注意到它不应该编译之前对其进行优化,并且这enum是一种避免这种不符合编译器行为的方法:

enum { type_must_be_complete = sizeof(C) };
Run Code Online (Sandbox Code Playgroud)

选择enum而不仅仅是丢弃表达的另一种解释仅仅是个人偏好.

或者正如James T. Hugget 对这个答案的评论中建议的那样,"枚举可能是在编译时创建伪可移植错误消息的一种方式".


(1)对于不完整类型,默认生成的do-nothing析构函数是旧的问题std::auto_ptr.由于国际C++标准化委员会主席Herb Sutter撰写的这篇关于PIMPL习语的GOTW项目,它是如此阴险.当然,现在std::auto_ptr已经弃用了,人们会使用其他一些机制.

  • 枚举可以是在编译​​时创建伪可移植错误消息的方式. (4认同)