简体   繁体   English

范围内的临时生命周期表达式

[英]temporary lifetime in range-for expression

Consider a simple class A that can be used as a range: 考虑一个可以用作范围的简单类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;
};

If I make a temporary A in a range-for, it works exactly as I would hope: 如果我在范围内制作临时A ,它的工作原理与我希望的完全相同:

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

// output
A::begin A::end w o r k s ~A 

However, if I try to wrap the temporary: 但是,如果我尝试包装临时:

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 
^^

Why is A 's lifetime not extended for the full range-for expression, and how can I make that happen without resorting to making a copy of the A ? 为什么A的寿命没有扩展到全范围的表达式,如何在不诉诸副本A情况下实现这一目标?

Lifetime extension only occurs when binding directly to references outside of a constructor. 仅当直接绑定到构造函数外部的引用时,才会发生生命周期扩展。

Reference lifetime extension within a constructor would be technically challenging for compilers to implement. 构造函数中的引用生命周期扩展对于编译器来说在技术上具有挑战性。

If you want reference lifetime extension, you will be forced to make a copy of it. 如果您想要参考生命周期扩展,您将被迫复制它。 The usual way is: 通常的方法是:

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

In many contexts, wrap is itself a template: 在许多情况下, 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;
};

and if A is a Foo& or a Foo const& , references are stored. 如果AFoo&Foo const& ,则存储引用。 If it is a Foo , then a copy is made. 如果是Foo ,则复制。

An example of such a pattern in use would be if wrap where called backwards , and it returned iterators that where reverse iterators constructed from A . 使用这种模式的一个例子是如果backwards调用wrap ,它返回迭代器,其中反向迭代器由A构造。 Then temporary ranges would be copied into backwards , while non-temporary objects would be just viewed. 然后临时范围将被复制到backwards ,而非临时对象将被查看。

In theory, a language that allowed you to markup parameters to functions and constructors are "dependent sources" whose lifetime should be extended as long as the object/return value would be interesting. 理论上,允许您将参数标记为函数和构造函数的语言是“依赖源”,只要对象/返回值有趣,其生命周期就应该延长。 This probably is tricky. 这可能很棘手。 As an example, imagine new wrap( A{"works"} ) -- the automatic storage temporary now has to last as long as the free store wrap ! 举个例子,想象一下new wrap( A{"works"} ) - 自动存储临时现在必须持续与免费商店wrap一样长!

The reason the lifetime of the temporary is not extended is how the standard defines range-based for loops in 不延长临时生命周期的原因是标准如何定义基于范围的for循环

6.5.4 The range-based for statement [stmt.ranged] 6.5.4基于范围的语句[stmt.ranged]

1 For a range-based for statement of the form 1对于基于范围for形式的语句

for ( for-range-declaration : expression ) statement for ( range-declaration : expression ) 语句

let range-init be equivalent to the expression surrounded by parentheses range-init等同于括号括起来的表达式

( expression )

and for a range-based for statement of the form 而对于基于范围for形式的声明

for ( for-range-declaration : braced-init-list ) statement for ( range-declaration : braced-init-list ) 语句

let range-init be equivalent to the braced-init-list . range-init等同于braced-init-list In each case, a range-based for statement is equivalent to 在每种情况下,基于范围的for语句等同于

 { auto && __range = range-init; for ( auto __begin = begin-expr, __end = end-expr; __begin != __end; ++__begin ) { for-range-declaration = *__begin; statement } } 

Note that auto && __range = range-init; 请注意, auto && __range = range-init; would extend the lifetime of a temporary returned from range-init , but it does not extend the lifetime of nested temporaries inside of range-init . 将延伸的临时从范围-INIT返回的寿命,但是它不嵌套的临时的寿命延长范围-INIT内部

This is IMHO a very unfortunate definition and was even discussed as Defect Report 900 . 这是恕我直言,这是一个非常不幸的定义,甚至被讨论为缺陷报告900 It seems to be the only part of the standard where a reference is implicitly bound to extend the lifetime of an expressions result without extending the lifetime of nested temporaries. 它似乎是标准的唯一部分,其中引用被隐式绑定以延长表达式结果的生命周期而不延长嵌套临时值的生命周期。

The solution is to store a copy in the wrapper - which often defeats the purpose of the wrapper. 解决方案是在包装器中存储一个副本 - 这经常会破坏包装器的用途。

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

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