手动终止基类子对象生命周期后,完整对象访问合法性及指针引用差异探讨
C++中销毁基类子对象后指针与引用访问派生类成员的行为判定
示例代码
struct Base { int b; }; struct Derived : Base { int d; }; int access_by_ptr() { auto *obj = new Derived{1, 2}; static_cast<Base *>(obj)->~Base(); return obj->d; // #1 } int access_by_ref() { auto &obj = *new Derived{1, 2}; static_cast<Base &>(obj).~Base(); return obj.d; // #2 }
问题与你的分析
依据C++标准,上述代码中#1和#2的行为是否定义良好?
你的分析如下:
- 针对#1:根据[basic.life]/6条款,对象生命周期结束后,指向其存储地址的指针仅能在有限场景下使用,不包括类成员访问。假设Base子对象与Derived对象地址相同(常见实现方式),此时
obj指针指向已结束生命周期的Base子对象存储地址,用它访问成员d属于标准明确禁止的行为,因此#1存在未定义行为。 - 针对#2:根据[basic.life]/7条款,仅指向已销毁对象的左值会被限制使用。
access_by_ref中的obj引用仍指向Derived对象,并未受Base子对象销毁的影响,因此#2的行为是合法的。
你同时提出疑问:上述理解是否正确?若正确,指针与引用在此场景下的细微差异是否是标准的有意设计?
理解正确性判定
你的理解是完全正确的:
- #1的行为属于未定义:显式调用Base子对象的析构函数后,该子对象的生命周期结束。根据[basic.life]/6,指向已销毁对象存储的指针,仅允许执行有限操作(如转换为
void*、在原存储位置重建对象等),通过指针访问类成员不在允许范围内——即便Base与Derived对象地址重合,标准也明确禁止这种访问,因此#1的行为未定义。 - #2的行为定义良好:
obj是绑定到完整Derived对象的左值引用,显式销毁Base子对象并不会终止Derived对象的生命周期(本例中Base析构函数无特殊操作)。根据[basic.life]/7,只有指向已销毁对象的左值才会被限制操作,而obj始终指向存活的Derived对象,因此访问obj.d是合法的。
指针与引用差异的设计意图
这种差异是标准的有意设计,核心源于指针与引用的语义本质区别:
- 指针是存储地址的变量,其指向的语义可能因类型转换、对象生命周期变化产生歧义(比如本例中,指针的静态类型是
Derived*,但指向的存储中Base子对象已销毁),标准需要明确这种场景下的操作限制,避免语义模糊。 - 引用是对象的别名,一旦绑定就始终指向初始对象(除非对象本身被销毁),只要原对象生命周期未结束,引用的语义就保持清晰。标准通过这种区分,明确了不同值类别在对象生命周期变化场景下的合法操作边界,贴合两者的语义定位。
内容的提问来源于stack exchange,提问作者Lancern
相关产品推荐
相关产品推荐

