在C ++ 11标准的12.2中:
引用绑定的临时值或临时值 引用绑定到的子对象的完整对象 在参考文件的生命周期内持续存在,除了:
临时约束 到构造函数的ctor-initializer中的引用成员(12.6.2) 持续到构造函数退出。
临时绑定到 函数调用(5.2.2)中的引用参数一直持续到 完成包含调用的完整表达式。
一生 临时绑定到函数返回中的返回值 声明(6.6.3)未予延长;临时被摧毁了 return语句中的完整表达式的结尾。
- 醇>
暂时的 绑定到new-initializer(5.3.4)中的引用一直持续到 完成包含new-initializer的完整表达式。
标准中有最后一个案例:
struct S {
int mi;
const std::pair<int,int>& mp;
};
S a { 1,{2,3} }; // No problem.
S* p = new S{ 1, {2,3} }; // Creates dangling reference
对我来说,2. and 3.
有道理且易于同意。但是,1. and 4.
的原因是什么?这个例子对我来说看起来很邪恶。
答案 0 :(得分:5)
与C和C ++中的许多内容一样,我认为这归结为可以合理(和有效)实现的内容。
Temporaries通常在堆栈上分配,并且调用其构造函数和析构函数的代码将被发送到函数本身。因此,如果我们将您的第一个示例扩展到编译器实际执行的操作,它将类似于:
struct S {
int mi;
const std::pair<int,int>& mp;
};
// Case 1:
std::pair<int,int> tmp{ 2, 3 };
S a { 1, tmp };
编译器可以很容易地将tmp
临时的生命周期延长到足以保持&#34; S&#34;是有效的,因为我们知道&#34; S&#34;将在函数结束前销毁。
但这并不适用于新的S&#34;情况下:
struct S {
int mi;
const std::pair<int,int>& mp;
};
// Case 2:
std::pair<int,int> tmp{ 2, 3 };
// Whoops, this heap object will outlive the stack-allocated
// temporary!
S* p = new S{ 1, tmp };
为了避免悬空引用,我们需要在堆上而不是堆栈上分配临时值,如:
// Case 2a -- compiler tries to be clever?
// Note that the compiler won't actually do this.
std::pair<int,int> tmp = new std::pair<int,int>{ 2, 3 };
S* p = new S{ 1, tmp };
但是相应的delete p
需要释放这个堆内存!这与引用的行为完全相反,并且会破坏使用普通引用语义的任何内容:
// No way to implement this that satisfies case 2a but doesn't
// break normal reference semantics.
delete p;
所以问题的答案是:规则是这样定义的,因为它是C ++关于堆栈,堆和对象生命周期的语义的唯一实用解决方案。
警告:@Potatoswatter在下面指出,这似乎并非在C ++编译器中一致地实现,因此目前最不可移植。看看他的例子,说明Clang如何做标准似乎在这里要求的。他还说,情况可能比那更糟糕。 - 我不确切地知道这意味着什么,但实际上C ++中的这种情况似乎存在一些不确定性。
答案 1 :(得分:2)
主要推力是参考扩展仅在生命周期可以轻松且确定性地确定时发生,并且这个事实可以在创建临时的代码行上被推断为 。
调用函数时,它会扩展到当前行的末尾。这很长,很容易确定。
当您在“堆栈”上创建自动存储引用时,可以确定性地确定该自动存储引用的范围。临时可以在那时清理。 (基本上,创建一个匿名自动存储变量来存储临时值)
在new
表达式中,无法在创建时静态确定破坏点。每当delete
发生时。如果我们希望delete
(有时)破坏临时,那么我们的引用“二进制”实现必须比指针更复杂,而不是更少或相等。它有时会拥有所引用的数据,有时则不会。这是一个指针,加上bool
。在C ++中,你不需要支付你不使用的东西。
在构造函数中也是如此,因为您无法知道构造函数是在new
还是堆栈分配中。因此,在相关行中无法静态理解任何生命周期延长。
答案 2 :(得分:0)
您希望临时对象持续多长时间?它必须在某处分配。
它不能在堆上,因为它会泄漏;没有适用的自动内存管理。它不能是静态的,因为可以有多个。它必须在堆栈上。然后它会持续到表达式结束或函数结束。
表达式中的其他临时值,可能绑定到函数调用参数,在表达式结束时被销毁,并持续到函数结束或“{}
”作用域将是一般规则的例外。因此,通过对其他案例的推论和推断,全表达是最合理的生命周期。
我不确定你为什么说这没问题:
S a { 1,{2,3} }; // No problem.
无论您是否使用new
,悬挂参考都是相同的。
检测程序并在Clang中运行它会产生以下结果:
#include <iostream>
struct noisy {
int n;
~noisy() { std::cout << "destroy " << n << "\n"; }
};
struct s {
noisy const & r;
};
int main() {
std::cout << "create 1 on stack\n";
s a {noisy{ 1 }}; // Temporary created and destroyed.
std::cout << "create 2 on heap\n";
s* p = new s{noisy{ 2 }}; // Creates dangling reference
}
create 1 on stack
destroy 1
create 2 on heap
destroy 2
绑定到类成员引用的对象没有延长的生命周期。
其实我确定这是标准中已知缺陷的主题,但我现在没时间钻研......