为什么std :: reference_wrapper <const T>不接受临时?

isa*_*ndi 14 c++ const rvalue c++11 reference-wrapper

通常,rvalues可以绑定到const引用(const SomeType&).它内置于语言中.但是,std::reference_wrapper<const T>不接受rvalue作为其构造函数参数,因为故意删除了相应的重载.这种不一致的原因是什么?std::reference_wrapper当我们必须传递值但希望保留引用语义时,"广告"作为引用变量的替代.

换句话说,如果const &绑定的右值被认为是安全的,因为它内置于语言中,为什么C++ 11的设计者不允许包含rvalues std::reference_wrapper<const T>

什么时候会派上用场,你可能会问.例如:

class MyType{};

class Foo { 
public:
    Foo(const MyType& param){} 
};

class MultiFoo {
public:
    MultiFoo(std::initializer_list<std::reference_wrapper<const MyType>> params){} 
};

int main()
{
    Foo foo{MyType{}}; //ok
    MultiFoo multiFoo{MyType{}, MyType{}}; //error
}
Run Code Online (Sandbox Code Playgroud)

Fil*_*efp 10

介绍

通常T const&并且T&&可以延长直接绑定到它的临时的生命周期,但如果引用 "隐藏"在构造函数后面,则这不适用.

由于std::reference_wrapper是可复制的(按意图),如果std::reference_wrapper以句柄转义创建临时的范围的方式使用,则引用对象的句柄可以比临时对象更长.

这将导致句柄和被引用 对象(即临时对象)之间的寿命不匹配.


让我们玩" 相信 "

想象一下,下面是非法的片段; 我们假装std::reference_wrapper有一个接受临时的构造函数.

让我们假装传递给构造函数的临时代码将延长其生命周期(即使不是这种情况,在现实生活中它将在之后"死" (1)).


typedef std::reference_wrapper<std::string const> string_ref;
Run Code Online (Sandbox Code Playgroud)

string_ref get_ref () {
  string_ref temp_ref { std::string { "temporary" } }; // (1)

  return temp_ref; 
}
Run Code Online (Sandbox Code Playgroud)

int main () {
  string_ref val = get_ref ();

  val.get (); // the temporary has been deconstructed, this is dangling reference!
}
Run Code Online (Sandbox Code Playgroud)

由于临时创建具有自动存储持续时间,因此将在绑定到范围内的存储上分配get_ref.

get_ref以后返回时,我们的临时将被销毁.这意味着我们的valin main将引用一个无效的对象,因为原始对象不再存在.

以上是std::reference_wrapper构造函数没有接受临时值的重载的原因.


另一个例子

struct A {
  A (std::string const& r)
    : ref (r)
  { }

  std::string const& ref;
};

A foo { std::string { "temporary " } };

foo.ref = ...; // DANGLING REFERENCE!
Run Code Online (Sandbox Code Playgroud)

std::string { "temporary" }不会延长使用寿命,可以在标准中阅读.

12.2p5 临时对象 [class.temporary]

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

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

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

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

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

注意:重要的是要注意构造函数只不过是一个"花哨"函数,它被称为对象构造.


Bri*_*ian 9

将临时直接绑定到引用会延长其生命周期.

struct Foo {};
Foo f() { return {}; }

void g() {
    f(); // temporary destroyed at end of full-expression
    const Foo& r = f(); // temporary destroyed at end of scope
    // r can still be used here ...
    // ...
    // all the way down to here
}
Run Code Online (Sandbox Code Playgroud)

但是,它必须直接绑定到临时.请考虑以下示例:

struct Bar {
    Bar(const Foo& f): r(f) {}
    const Foo& r;
};

void h() {
    Bar b(f());
    // binding occurs through parameter "f" rather than directly to temporary "f()"
    // b.r is now a dangling reference! lifetime not extended
}
Run Code Online (Sandbox Code Playgroud)

即使你可以,这将使包装临时包装变得毫无用处.

注意:实际的引用包装器对象使用指针,以便可以重新分配:

struct Baz {
    Baz(const Foo& f): p(std::addressof(f)) {}
    Baz& operator=(const Foo& f) { p = std::addressof(f); return *this; }
    const Foo* p;
};
Run Code Online (Sandbox Code Playgroud)

同样的情况仍然适用:传递给构造函数或赋值运算符的临时函数将在包含调用的完整表达式的末尾被销毁.