为何允许将临时值绑定到const引用?C++语法疑问
临时对象绑定到const引用的C++语法规则与陷阱
问题背景
接手一个大量使用const引用的代码库时遇到了一个隐蔽的bug:将函数返回的临时对象传递给接收const引用的构造函数能通过编译,但后续访问该引用会导致数据损坏。以下是最小复现示例:
基础定义
typedef struct { int a; int b; } Data; Data Make_Data(int aVal) { Data ret = { aVal, 2*aVal }; return ret; }
普通引用的编译错误
当尝试将临时对象传递给接收非const引用的构造函数时,编译器直接报错:
class Bar { public: Bar(Data& theData) : data(theData) {}; Data& data; }; int main() { Bar *bar1 = new Bar(Make_Data(5)); // 错误:构造函数需要左值作为第一个参数 }
const引用的编译通过但存在隐患
而传递给接收const引用的构造函数时,编译完全通过,但运行时会出现未定义行为:
class Foo { public: Foo(const Data& theData) : data(theData) {}; const Data& data; }; int main() { Foo *foo1 = new Foo(Make_Data(1)); // 后续访问foo1->data会指向已销毁的临时对象,导致数据损坏 }
为什么允许临时值传递给const引用?
这是C++标准明确规定的语法规则,核心在于安全与效率的平衡:
- 临时对象是右值(无法被修改的临时实体),非const左值引用(
Data&)不能绑定右值——因为非const引用意味着程序员可能会修改引用指向的对象,但修改临时对象没有意义(修改后对象立刻销毁),编译器直接禁止这种无意义且可能误导的操作。 - const左值引用(
const Data&)承诺不会修改指向的对象,因此编译器允许它绑定右值。这样既可以避免拷贝临时对象的开销(尤其是大对象时),又能保证不会意外修改临时对象,在语法上是安全的。
为什么生命周期延长的规则没生效?
你提到的临时对象生命周期延长有严格适用条件:只有当临时对象直接绑定到局部作用域的const引用时,生命周期才会延长到该引用的生命周期结束。但你的场景不满足规则,原因如下:
- 临时对象
Make_Data(1)首先绑定到构造函数的参数const Data& theData——这个参数是构造函数的局部引用,此时临时对象的生命周期确实会延长到构造函数执行完毕。 - 但将该参数用来初始化类的成员引用
data时,类成员引用并不会继承临时对象的生命周期延长。C++标准明确规定:如果临时对象绑定到构造函数的参数,且该参数用于初始化类的成员引用,临时对象的生命周期不会延长到类对象的生命周期。 - 构造函数执行完成后,局部参数
theData销毁,临时对象也随之销毁,类成员data就变成了悬空引用,后续访问属于未定义行为(可能出现数据损坏、程序崩溃等)。
代码规范的提醒
CppCoreGuidelines的C.12准则明确指出:不要将引用作为类成员,除非是用于观察者模式或能明确管理引用生命周期的场景。你的代码正是违反了这一准则——类成员引用依赖外部对象的生命周期,但这里外部对象是临时对象,生命周期远短于类对象,必然导致悬空引用问题。
完整可复现代码
#include <iostream> #include <string> typedef struct { int a; int b; } Data; Data Make_Data(int aVal, int bVal) { Data ret = { aVal, bVal }; return ret; } class Foo { public: Foo(const Data& theData) : data(theData) {}; const Data& data; }; class Bar { public: Bar(Data& theData) : data(theData) {}; Data& data; }; int main() { Foo *foo1 = new Foo(Make_Data(1, 2)); Foo *foo2 = new Foo(Make_Data(3, 4)); //Bar *bar1 = new Bar(Make_Data(5, 6)); //error: candidate constructor not viable: expects an lvalue for 1st argument std::cout << "a=" << foo1->data.a << ", b=" << foo1->data.b << std::endl; std::cout << "a=" << foo2->data.a << ", b=" << foo2->data.b << std::endl; delete foo1; delete foo2; }
内容的提问来源于stack exchange,提问作者Heath Raftery
相关产品推荐
相关产品推荐

