繁体   English   中英

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

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

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

换句话说,如果const & binding的rvalue被认为是安全的,因为它内置于语言中,为什么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
}

介绍

通常, 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]

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

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

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

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

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

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

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

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&冒险或差异)。 更重要的是,如果相应的临时不再存在,那么后面使用该引用可能是危险的(实际上, 它是危险的 ,因为它在这种情况下调用未定义的行为)。

毕竟删除它的rvalue-reference构造函数并不是一个坏主意。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM