在析构函数中的奇怪的枚举

目前,我正在阅读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; } } 

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

这个技巧通过确保在编译析构函数时确定C的定义是可用的,从而避免了UB。 否则编译将会失败,因为不完整types的sizeof (前向声明types)不能被确定,但是可以使用指针。

在编译的二进制文件中,这个代码将会被优化,并且不会起作用。

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

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

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

警告:在调用删除操作符时检测到可能的问题:
警告:“p”的types不完整
警告:“struct C”的前向声明

如果C不是一个完整的types, sizeof(C)将在编译时失败。 设置一个本地作用域enum使它在运行时是良性的。

这是程序员保护自己的一种方式:如果一个不完整types的后续delete ptr_的行为有一个不平凡的析构函数,那么它是不确定的。

为了理解这个enum ,首先考虑没有它的析构函数:

 ~scoped_ptr() { delete ptr_; } 

其中ptr_是一个C* 。 如果typesC在这一点上是不完整的,即编译器知道的所有struct C; ,那么(1)为所指向的C实例使用默认生成的无作用的析构函数。 对于由智能指针pipe理的对象来说,这不太可能是正确的。

如果通过指向不完整types的指针删除总是有未定义的行为,那么标准可能只是要求编译器诊断它并失败。 但是,当真正的析构函数是微不足道的时候,它是非常明确的:程序员可以拥有的知识,但编译器没有的知识。 为什么这个语言定义并允许这个超越了我,但是C ++支持许多当今不被认为是最佳实践的实践。

一个完整的types有一个已知的大小,因此, sizeof(C)将会编译当且仅当C是一个完整的types – 具有已知的析构函数。 所以它可以用作警卫。 一种方法是简单的

 (void) sizeof(C); // Type must be complete 

猜测用一些编译器和选项,编译器会优化它,然后才能注意到它不应该编译,并且enum是避免这种不合适的编译器行为的一种方法:

 enum { type_must_be_complete = sizeof(C) }; 

selectenum的另一种解释是简单的个人偏好。

或者像James T. Hugget 在对这个答案的评论中所build议的那样,“枚举可能是在编译时创build伪随身错误消息的一种方法”。


(1)对于一个不完整的types,默认生成的什么都不做的析构函数是旧的std::auto_ptr的问题。 这是非常阴险的,它进入了一个由国际C ++标准化委员会主席Herb Sutter编写的关于PIMPL习语的GOTW项目 。 当然,现在std::auto_ptr已经被弃用了,反而会使用其他一些机制。

也许有一个技巧来确定C被定义。