【问题标题】:How do I extend the lifetime of a temporary in a ranged for expression?如何延长范围表达式中临时变量的生命周期?
【发布时间】:2016-01-14 11:53:58
【问题描述】:

我在使用 ranged-for 循环时得到了悬空引用。考虑以下 C++14 表达式(下面的完整示例程序):

    for(auto& wheel: Bike().wheels_reference())
        wheel.inflate();

它的输出是:

 Wheel()
 Wheel()
 Bike()
~Bike() with 0 inflated wheels.
~Wheel()
~Wheel()
 Wheel::inflate()
 Wheel::inflate()

显然有些地方出了问题。轮子在其生命周期之外被访问,结果为 0,而不是预期的 2。

一个简单的解决方法是在main 中为Bike 引入一个变量。但是,我不控制mainWheel 中的代码。我只能更改结构Bike

有什么方法可以通过只更改Bike 来解决这个例子吗?

一个成功的解决方案要么在编译时失败,要么计算 2 个充气轮胎并且在其生命周期之外不接触任何对象。

附录:编译准备好的源码

#include <cstdlib>
#include <iostream>
#include <array>
#include <algorithm>
using std::cout;
using std::endl;

struct Wheel
{
    Wheel() { cout << " Wheel()" << endl; }
    ~Wheel() { cout << "~Wheel()" << endl; }
    void inflate() { inflated = true; cout << " Wheel::inflate()" << endl; }
    bool inflated = false;
};

struct Bike
{
    Bike() { cout << " Bike()" << endl; }
    ~Bike() {
        cout << "~Bike() with " << std::count_if(wheels.begin(),    wheels.end(),
            [](auto& w) { return w.inflated; }) << " inflated wheels." << endl;
    }
    std::array<Wheel, 2>& wheels_reference() { return wheels; }
    std::array<Wheel, 2> wheels{Wheel(), Wheel()};
};

int main()
{
    for(auto& wheel: Bike().wheels_reference())
        wheel.inflate();
    return EXIT_SUCCESS;
}

【问题讨论】:

  • 如果只在Bike 中,函数reference 应该按值返回数组,而不是按引用,但我认为,这不是你想要的。
  • @ForEveR 但auto&amp; 无法绑定。
  • @ForEveR:虽然它解决了销毁后使用,但它不满足我的解决条件。打印的值为 0,而不是 2。
  • @TartanLlama 但同时在 gcc/clang 中绑定。
  • @ForEveR 呵呵,我猜是大脑编译器坏了。

标签: c++ reference c++14 ranged-loops


【解决方案1】:

删除wheels_reference的右值重载。

std::array<Wheel, 2>& wheels_reference() & { return wheels; }
std::array<Wheel, 2>& wheels_reference() && = delete;

这样您就不会返回对临时成员的引用。

Bike 类的示例用法

for(auto& wheel: Bike().wheels_reference())
    wheel.inflate();

然后将拒绝编译(clang 3.4 输出):

test.cpp:31:29: error: call to deleted member function 'wheels_reference'
    for(auto& wheel: Bike().wheels_reference())
                     ~~~~~~~^~~~~~~~~~~~~~~~
test.cpp:24:27: note: candidate function has been explicitly deleted
    std::array<Wheel, 2>& wheels_reference() && = delete;
                          ^
test.cpp:23:27: note: candidate function not viable: no known conversion from 'Bike' to 'Bike' for object argument
    std::array<Wheel, 2>& wheels_reference() & { return wheels; }

如果临时的生命周期是手动延长的,那么事情就会起作用。

Bike&& bike = Bike();
for(auto& wheel: bike.wheels_reference())
    wheel.inflate();

【讨论】:

  • 我对不存在的函数和“std::array&wheels_reference() && = delete;”感到困惑
  • @DieterLücking 检查此en.cppreference.com/w/cpp/language/…
  • 为什么要删除&amp;&amp;重载?您可以只定义单个 &amp; 重载。
  • @Orient 你是对的,但是我在只提供 lvalue-ref 重载时得到的错误消息并不好:clang 给出了最无用的error: cannot initialize object parameter of type 'Bike' with an expression of type 'Bike' 而 g++5 告诉我@ 987654331@。显式删除重载要清晰得多。
  • @jepio 没错。
【解决方案2】:

最好的解决方案是停止通过临时调用成员函数来获取某个类型的成员。

如果wheels_reference 是一个非成员函数,你可以简单地这样声明它:

wheels_reference(Bike &bike);

由于非 const 左值参数不能附加到临时参数,您将无法调用 wheels_reference(Bike())。因为wheels_reference 是一个成员函数,你只需要使用成员函数语法来表达同样的事情:

std::array<Wheel, 2>& wheels_reference() & //<--
{ return wheels; }

如果用户现在尝试调用Bike().wheels_reference(),编译器会报错。

【讨论】:

  • 完美!与@jeplo 的解决方案基本相同。与非成员函数的类比很有见地。
【解决方案3】:

以下可怕的装置似乎满足所有条件:

#include <memory>

struct Bike
{
    // Bike() { cout << " FakeBike()" << endl; }
    // ~Bike() { cout << "~FakeBike()" << endl; }
    struct RealBike;
    struct Wrap {
        std::shared_ptr<RealBike> parent;
        auto begin() { return parent->wheels.begin(); }
        auto end() { return parent->wheels.end(); }
    };
    struct RealBike {
        RealBike() { cout << " Bike()" << endl; }
        ~RealBike() {
            cout << "~Bike() with " << std::count_if(wheels.begin(),    wheels.end(),
                [](auto& w) { return w.inflated; }) << " inflated wheels." << endl;
        }
        std::array<Wheel, 2> wheels;
    };
    std::shared_ptr<RealBike> real = std::make_shared<RealBike>();
    Wrap wheels_reference() { return Wrap{real}; }
};

我不喜欢的是它需要将std::array&lt;Wheel, 2&gt;的所有API封装在Wrap中。

【讨论】:

    【解决方案4】:

    您可以添加一对wheels_reference 成员函数的 cv-ref 限定重载:

    std::array<Wheel, 2>& wheels_reference() & { return wheels; }
    
    std::array<Wheel, 2> const & wheels_reference() const & { return wheels; }
    
    std::array<Wheel, 2> wheels_reference() && { return std::move(wheels); }
    
    std::array<Wheel, 2> wheels_reference() const && { return wheels; }
    

    注意,如果对象是临时的(&amp;&amp; 情况),那么您应该返回一个值(从数据成员复制构造,或者更好地从它移动构造),也不应该引用。

    拥有所有四个重载,您可以覆盖所有可能的用例。

    【讨论】:

      猜你喜欢
      • 2015-05-03
      • 1970-01-01
      • 2013-11-20
      • 2015-07-31
      • 2012-06-23
      • 2012-05-19
      • 2013-08-07
      • 1970-01-01
      • 2023-03-21
      相关资源
      最近更新 更多