C++ 表达式的临时生存期在范围内

C++ 表达式的临时生存期在范围内,c++,c++11,C++,C++11,考虑一个可以用作范围的简单类a: struct A { ~A() { std::cout << "~A "; } const char* begin() const { std::cout << "A::begin "; return s.data(); } const char* end() const { std::cout << "A::end ";

考虑一个可以用作范围的简单类
a

struct A { 
    ~A() { std::cout << "~A "; }

    const char* begin() const {
        std::cout << "A::begin ";
        return s.data();
    }   

    const char* end() const {
        std::cout << "A::end ";
        return s.data() + s.size();
    }   

    std::string s;
};
但是,如果我尝试包装临时文件:

struct wrap {
    wrap(A&& a) : a(std::move(a))
    { } 

    const char* begin() const { return a.begin(); }
    const char* end() const { return a.end(); }

    A&& a;
};

for (auto c : wrap(A{"fails"})) {
    std::cout << c << ' ';
}

// The temporary A gets destroyed before the loop even begins: 
~A A::begin A::end 
^^
struct wrap{
换行(A&&A):A(标准::移动(A))
{ } 
const char*begin()const{返回a.begin();}
const char*end()const{return a.end();}
A&A;
};
for(自动c:wrap(A{“fails”})){

std::cout仅当直接绑定到构造函数外部的引用时才会发生生存期扩展

构造函数中的引用生存期扩展对于编译器实现来说是一项技术挑战

如果您想要引用生存期延长,您将被迫复制它。通常的方法是:

struct wrap {
  wrap(A&& a) : a(std::move(a))
  {} 

  const char* begin() const { return a.begin(); }
  const char* end() const { return a.end(); }

  A a;
};
在许多情况下,
wrap
本身就是一个模板:

template<class A>
struct wrap {
  wrap(A&& a) : a(std::forward<A>(a))
  {} 

  const char* begin() const { return a.begin(); }
  const char* end() const { return a.end(); }

  A a;
};
模板
结构包裹{
换行(A&&A):A(标准::向前(A))
{} 
const char*begin()const{返回a.begin();}
const char*end()const{return a.end();}
A A;
};
如果
A
Foo&
Foo const&
,则存储引用。如果它是
Foo
,则制作副本

使用这种模式的一个例子是,如果
wrap
在其中调用
backward
,并且它返回迭代器,其中从
a
构造的反向迭代器。那么临时范围将被复制到
backwardward
,而非临时对象将只被查看


理论上,允许您将参数标记到函数和构造函数的语言是“依赖源”,只要对象/返回值有趣,它的生存期就应该延长。这可能很棘手。例如,想象一下
newwrap(a{“works”})
--自动存储临时存储现在必须与免费存储的时间一样长
wrap

临时存储的生存期没有延长的原因是标准如何定义循环的范围

6.5.4基于语句的范围[stmt.ranged] 1用于表格中基于范围的
语句

用于(
用于范围声明
表达式
语句

让range init等价于用括号括起来的表达式

(表达式)

对于表单的
语句,以及基于范围的

用于(
用于范围声明
带括号的初始化列表
语句

让range init等效于带括号的init列表。在每种情况下,基于range的
for
语句等效于

{
   auto && __range = range-init;
   for ( auto __begin = begin-expr,
              __end = end-expr;
         __begin != __end;
         ++__begin ) {
      for-range-declaration = *__begin;
      statement
   }
}
请注意,
auto&&&uu range=range init;
将延长range init返回的临时对象的生存期,但不会延长range init内嵌套临时对象的生存期

这是一个非常不幸的定义,甚至被讨论为。它似乎是标准中唯一一个引用隐含地扩展表达式结果的生存期而不扩展嵌套临时表的生存期的部分


解决方案是在包装器中存储一个副本-这通常会破坏包装器的用途。

有一个成员变量T&&是错误的,这是永远不会起作用的。
a
是一个悬空引用,只要
wrap
构造函数调用endsNote
a(std::move(a))
实际上并不移动对象;它只是将其强制转换。换句话说,生命周期扩展不能被链接。只能进行一次。因此,没有办法向后写入
来获取临时对象而不复制它?如果
wrap
没有提供显式构造函数,
wrap{a{}将很好地工作,因为我将直接绑定到<代码> r> 中的rValf Ref——所以看起来像这样的事情应该是可能的。“巴里当然可以,但不是C++中的。即使是<代码>包装{a} } /代码>也不是我上次检查的编译器所支持的(而且我猜的是从一个角落的例子中掉出来)。.根据我的经验,只有少数情况下这是一个问题:
intarr[3]
std::array
。大多数其他容器的移动成本都很低。对于数组,我倾向于传递数组视图,而数组视图的移动成本也很低。仅当绑定到的对象是临时对象时,才需要存储这样一个副本,这会有所帮助。@Yakk-Right,但您无法真正检测它是表达式的真正临时对象还是仅仅是一个临时对象n右值引用具有较长生命周期的内容。为了安全起见,您需要存储一个副本(真实副本或移动,具体取决于您获得的引用类型)。如果它是对“具有较长生命周期”的内容的右值引用,则您正在作出隐式承诺(通过使用右值引用)您将不会保留引用。调用方应该可以自由地将该存储用于其他用途,而不必担心将指针插入其内部。当您无法完全控制结构的生命周期和传入的右值引用时,在内部保留右值引用是个坏主意。
{
   auto && __range = range-init;
   for ( auto __begin = begin-expr,
              __end = end-expr;
         __begin != __end;
         ++__begin ) {
      for-range-declaration = *__begin;
      statement
   }
}