含虚函数类右值的const引用调用引发异常的原理与相关疑问
右值绑定到const引用后的生命周期与虚函数调用问题
原错误代码
class A { public: virtual uint32_t getNumber() const { return 0; }; }; class B { public: B( const A& a ) : a_( a ) {} const A& a_; void test() { a_.getNumber(); // 调用时触发异常 } }; B b{ A{} }; int main() { b.test(); return 0; }
现象说明
使用MSVC编译器编译执行上述代码时,调用a_.getNumber()会抛出异常;将getNumber()改为非虚函数后,代码可正常运行:
class A { public: uint32_t getNumber() const { return 0; }; };
一、问题背后的核心机制
- 悬垂引用的产生:
B b{ A{} };创建了一个临时A对象(右值),它被绑定到B构造函数的const引用参数a。但B类的成员a_只是复制了这个引用,并没有直接绑定到临时对象。根据C++规则,临时对象的生命周期仅被延长至直接绑定的引用的作用域结束——也就是B构造函数执行完毕后,临时A对象就会被销毁,b.a_随即变成悬垂引用。 - 虚函数与非虚函数的差异:
- 调用虚函数时,需要通过对象实例中的虚表指针(vptr)查找虚表(vtable)来确定函数地址。临时对象销毁后,
a_指向的内存已无效,访问vptr会触发未定义行为(MSVC表现为抛出异常)。 - 非虚函数是静态绑定,编译期就确定了函数地址,调用时不需要访问对象的任何内存(本例中函数仅返回固定值),所以即使引用悬垂,也可能“正常运行”——但这本质仍是未定义行为,只是巧合没有触发错误。
- 调用虚函数时,需要通过对象实例中的虚表指针(vptr)查找虚表(vtable)来确定函数地址。临时对象销毁后,
二、右值的const引用有效期规则
C++标准明确:
- 当右值直接绑定到const左值引用时,临时对象的生命周期会被延长至该引用的生命周期结束,比如
const A& ref = A{};,临时A会和ref共存亡。 - 若引用是间接绑定(比如通过另一个引用传递、赋值),则生命周期不会被延长,临时对象会在其原始作用域结束时销毁(本例中就是B构造函数执行完毕时)。
三、不同编译器的处理差异
这种场景属于未定义行为,C++标准没有强制规定程序的表现,因此不同编译器的处理可能完全不同:
- MSVC会检测到悬垂引用访问虚表的非法操作,抛出异常。
- 部分编译器在特定优化级别下,可能因为临时对象的内存未被立即覆盖、非虚函数被内联等原因,让程序看似正常运行,但这完全是偶然情况,绝对不能依赖。
内容的提问来源于stack exchange,提问作者Oliver S.
相关产品推荐
相关产品推荐

