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

Why does std::reference_wrapper<const T> not accept a temporary?

本文关键字:不接受 gt const wrapper std lt 为什么 reference      更新时间:2023-10-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
}

介绍

通常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稍后返回时,临时函数将被销毁。这意味着main中的val将引用一个无效的对象,因为原来的对象已经不存在了。

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

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

  • 在构造函数的参数初始化式(12.6.2)中,到引用成员的临时绑定一直存在,直到构造函数退出。

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

  • 在函数返回语句(6.6.3)中临时绑定到返回值的生存期不被延长;临时变量在返回语句的完整表达式结束时销毁。

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

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

将临时对象直接绑定到引用可以延长其生存期。

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;
};

仍然适用:传入构造函数或赋值操作符的临时变量将在包含调用的完整表达式结束时销毁。

不应该复制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
}

const引用对Foo的构造函数保持临时X{3}存活,但对对象本身无效。你得到一个悬空引用。

为了保护您避免这个问题,std::reference_wrapperstd::ref的临时对象的使用已被禁用。

由于const T&&变量不能移动,也不能修改,所以没有理由使用它(与const T&相比没有优势或差异)。更重要的是,如果相应的临时对象不再存活,那么后置使用该引用可能是危险的(实际上,是危险的,因为在这种情况下它调用未定义的行为)。

删除右值引用构造函数毕竟是个好主意。