目前,我正在阅读源代码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
已经弃用了,人们会使用其他一些机制.
归档时间: |
|
查看次数: |
3549 次 |
最近记录: |