c++ 范围表达式中的临时生存期

vawmfj5a  于 2023-03-25  发布在  其他
关注(0)|答案(3)|浏览(108)

考虑一个简单的类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;
};

如果我在range-for中创建一个临时的A,它就像我希望的那样工作:

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

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

但是,如果我尝试 Package 临时的:

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

为什么A的生存期没有为整个range-for表达式延长,我怎样才能做到这一点,而不需要复制A

nkoocmlb

nkoocmlb1#

临时变量的生存期没有延长的原因是标准在

6.5.4基于范围的for语句[stmt.ranged]

1对于形式为的基于范围的for语句
for (for-range-declaration:expression)statement
range-init 等效于括号中的 expression
( expression )
并且对于形式为的基于范围的for语句
for (for-range-declaration: * 大括号初始化列表 * ) * 语句 *
range-init 等价于 * 花括号初始化列表 *。在每种情况下,基于范围的for语句等价于

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

请注意,auto && __range = range-init;将延长从 range-init 返回的临时对象的生存期,但它不会延长 range-init 内部 * 嵌套临时对象的生存期。
恕我直言,这是一个非常不幸的定义,甚至作为缺陷报告900进行了讨论。它似乎是标准中唯一一个引用被***隐式***绑定以延长表达式结果的生存期而不延长嵌套临时变量的生存期的部分。
解决方案是在 Package 器中存储一个副本--这通常会违背 Package 器的目的。

lhcgjxsq

lhcgjxsq2#

生存期扩展仅在直接绑定到构造函数外部的引用时发生。
构造函数中的引用生存期扩展对于编译器实现来说在技术上具有挑战性。
如果你想延长引用的生存期,你将被迫复制它。通常的方法是:

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

如果AFoo&Foo const&,则存储引用。如果是Foo,则进行复制。
这种模式的一个例子是,如果wrap被调用为backwards,并且它返回的迭代器是从A构造的反向迭代器,那么临时范围将被复制到backwards中,而非临时对象将只被查看。
从理论上讲,一种允许你标记函数和构造函数参数的语言是“依赖源”,只要对象/返回值是有趣的,它的生存期就应该延长。这可能是很棘手的。作为一个例子,想象一下new wrap( A{"works"} )--自动存储临时存储现在必须和免费存储wrap一样长!

j9per5c4

j9per5c43#

好消息:从C++23开始,基于范围的for循环初始化器中临时变量的生存期已经延长到循环结束。参见p2718r0

相关问题