16

通常,右值可以绑定到 const 引用 ( const SomeType&)。它内置在语言中。但是,std::reference_wrapper<const T>不接受右值作为其构造函数参数,因为相应的重载被故意删除。这种不一致的原因是什么?std::reference_wrapper在我们必须按值传递但希望保留引用语义的情况下,被“宣传”为引用变量的替代方案。

换句话说,如果const &绑定的右值被认为是安全的,因为它是语言内置的,为什么 C++11 的设计者不允许右值被包装在 中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
}
4

4 回答 4

13

介绍

通常T const&并且可以延长直接绑定到它的临时对象的生命周期,但如果引用“隐藏”在构造函数后面T&&,则此方法不适用。

由于std::reference_wrapper是可复制的(有意地),如果std::reference_wrapper以这样的方式使用引用对象的句柄,则引用对象的句柄可以比临时对象的句柄寿命更长,从而使句柄逃脱了创建临时对象的范围。

这将导致句柄和引用的 对象(即临时对象)之间的生命周期不匹配。


让我们玩“相信

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

让我们也假设传递给构造函数的临时变量将延长其生命周期(即使不是这种情况,在现实生活中它会在之后“死” (1))。


typedef std::reference_wrapper<std::string const> string_ref;

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

  return temp_ref; 
}

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

  val.get (); // the temporary has been deconstructed, this is dangling reference!
}

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

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

以上就是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!

的生命周期std::string { "temporary" }不会延长,可以在标准中读取。

12.2p5 临时对象 [class.temporary]

引用绑定到的临时对象或作为引用绑定到的子对象的完整对象的临时对象在引用的生命周期内持续存在,但以下情况除外:

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

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

  • 临时绑定到函数返回语句 (6.6.3) 中的返回值的生命周期不会延长;临时在 return 语句中的完整表达式的末尾被销毁。

    • 临时绑定到new-initializer (5.3.4) 中的引用,直到包含new-initializer的完整表达式完成为止。

注意:重要的是要注意构造函数只不过是一个“花哨”函数,调用对象构造。

于 2014-05-31T18:24:38.930 回答
10

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

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
}

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

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
}

即使可以,这也会使包装临时文件变得毫无用处。

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

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

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

于 2014-05-31T18:26:29.057 回答
1

Since a const T&& variable can nor be moved, neither modified, there's no reason for using it (there's no adventages or differences over a const T&). Even more, a posterior use of that reference can be dangerous if the corresponding temporary is no longer alive (actually, it's dangerous, because it invokes undefined behaviour in such a case).

The deletion of its rvalue-reference constructor is not a bad idea after all.

于 2014-05-31T19:10:31.430 回答
1

您不应该复制 const 引用,因为它不一定使引用的对象保持活动状态。以下代码显示了问题:

#include <iostream>

struct X { int x; };

struct Foo {
    const X& a;
    Foo(const X& na) : a(na) {} // here na is still OK
};

int main()
{
    Foo foo{X{3}}; // the temporary exists only for this expression
    // now the temporary is no longer alive and foo.a is a dangling reference
    std::cout << foo.a.x << std::endl; // undefined behavior
}

X{3}const 引用为 的构造函数保持临时活动Foo,但不为对象本身保持活动状态。你得到一个悬空的参考。

std::reference_wrapper为了保护您免受此问题的影响,使用std::ref已禁用的临时对象。

于 2014-05-31T18:38:13.220 回答