绑定到引用时临时对象生命周期扩展异常的基本原理是什么?

upd*_*liu 6 c++ object-lifetime temporary-objects c++11

在C++ 11标准的12.2中:

绑定引用的临时对象或绑定引用的子对象的完整对象的临时对象在引用的生命周期内持续存在,除了:

  1. 绑定到构造函数的ctor-initializer(12.6.2)中的引用成员的临时绑定将持续存在,直到构造函数退出.

  2. 函数调用(5.2.2)中的引用参数的临时绑定将持续到包含该调用的完整表达式完成为止.

  3. 函数返回语句(6.6.3)中返回值临时绑定的生命周期未扩展; 临时在return语句中的full-expression结束时被销毁.

  4. 在new-initializer(5.3.4)中对引用的临时绑定将持续到包含new-initializer的full-expression完成为止.

并且标准中有最后一个案例:

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
Run Code Online (Sandbox Code Playgroud)

对我而言,2. and 3.理解并容易达成一致.但是原因是1. and 4.什么?这个例子对我来说看起来很邪恶.

Jos*_*man 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 };
Run Code Online (Sandbox Code Playgroud)

编译器可以很容易地延长tmp临时的寿命,使"S"保持有效,因为我们知道"S"将在函数结束之前被销毁.

但这不适用于"新S"案例:

  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 };
Run Code Online (Sandbox Code Playgroud)

为了避免悬空引用,我们需要在堆上而不是堆栈上分配临时值,例如:

   // 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 };
Run Code Online (Sandbox Code Playgroud)

但是后来相应的delete p需要释放这个堆内存!这与引用的行为完全相反,并且会破坏使用普通引用语义的任何东西:

  // No way to implement this that satisfies case 2a but doesn't
  // break normal reference semantics.
  delete p;
Run Code Online (Sandbox Code Playgroud)

所以你的问题的答案是:规则是这样定义的,因为它是C++围绕堆栈,堆和对象生命周期的语义的唯一实用解决方案.

警告:@Potatoswatter在下面指出,这似乎并没有在C++编译器中一致地实现,因此现在最好是不可移植的.请参阅他的例子,了解Clang如何不执行此标准似乎要求的内容.他还说情况"可能比这更糟糕" - 我不知道究竟是什么意思,但实际上C++中的这种情况似乎有一些不确定性.