临时生命和完美的转发构造函数

Thi*_*aut 8 c++ c++11

我无法理解为什么当有一个完美的转发构造函数时,绑定到const参考参数的临时工的生命周期被缩短.首先,我们知道有关临界参数的临时参数:它们持续完整表达式:

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

但是我发现这种情况并非如此(或者我可能只是误解了完整表达式是什么).让我们举一个简单的例子,首先我们用详细的构造函数和析构函数定义一个对象:

struct A {
  A(int &&) { cout << "create A" << endl; }
  A(A&&) { cout << "move A" << endl; }
  ~A(){ cout << "kill A" << endl; }
};
Run Code Online (Sandbox Code Playgroud)

还有一个对象包装器B,它将用于参考折叠:

template <class T> struct B {
  T value;
  B() : value() { cout << "new B" << endl; }
  B(const T &__a) : value(__a) { cout << "create B" << endl; }
  B(const B &p) = default;
  B(B && o) = default;
  ~B(){ cout << "kill B" << endl; };
};
Run Code Online (Sandbox Code Playgroud)

我们现在可以使用我们的包装器来捕获临时对象的引用并在函数调用中使用它们,如下所示:

void foo(B<const A&> a){ cout << "Using A" << endl; }
int main(){ foo( {123} ); }
Run Code Online (Sandbox Code Playgroud)

上面的程序打印出我期望的内容:

create A
create B
Using A
kill B
kill A
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.现在让我们回到B并为可转换类型添加一个完美的转发构造函数:

template <class T> struct B {
  /* ... */
  template <class U, class = typename enable_if<is_convertible<U, T>::value>::type>
    B(U &&v) : value(std::forward<U>(v)) { 
      cout << "new forward initialized B" << endl; 
    }
};
Run Code Online (Sandbox Code Playgroud)

现在再次编译相同的代码给出:

create A
new forward initialized B
kill A
Using A
kill B
Run Code Online (Sandbox Code Playgroud)

请注意,我们的A对象在使用之前就被杀死了,这很糟糕!为什么临时的生命周期没有扩展到foo这种情况下的完整调用?此外,没有其他对析构函数的调用A,因此没有其他实例.

我可以看到两种可能的解释:

  • 要么类型不是我认为的类型:更改可转换移动构造函数B(T &&v)而不是template <class U>B(U &&v)解决问题.
  • 或者{123}不是子表达式foo( {123} ).交换{123}A(123)也解决了问题,这使我怀疑,如果大括号初始化充满表情.

有人可以澄清这里发生了什么吗?

这是否意味着在某些情况下向类添加转发构造函数会破坏向后兼容性,就像它一样B

你可以在这里找到完整的代码,另一个测试用例崩溃以引用字符串.

eca*_*mur 5

推断该类型U的呼叫B<A const&>::B(U&&)int,这样的只是暂时的,可以是寿命扩展用于该呼叫到foomain是一个prvalue int临时初始化到123.

该成员A const& value绑定到临时A,但是在构造函数Amem-initializer-list中创建,B<A const&>::B(U&&)因此其生命周期仅在该成员初始化[class.temporary]/5 的持续时间内扩展:

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

请注意,mem-initializer-listctor-initializer中冒号后面的部分:

  template <class U, class = typename enable_if<is_convertible<U, T>::value>::type>
    B(U &&v) : value(std::forward<U>(v)) { 
             ^--- ctor-initializer
               ^--- reference member
                    ^--- temporary A
Run Code Online (Sandbox Code Playgroud)

这就是为什么kill A打印之后 new forward initialized B.

这是否意味着在某些情况下向类添加转发构造函数会破坏向后兼容性,就像它一样B

是.在这种情况下,很难理解为什么转发构造函数是必要的; 如果你有一个临时可以绑定的引用成员,那肯定是危险的.