如何在范围表达式中延长临时的生命周期?

时间:2016-01-14 11:53:59

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

我在使用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。

一个简单的解决方法是在Bike中引入main的变量。但是,我无法控制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;
}

4 个答案:

答案 0 :(得分:6)

删除wheels_reference的rvalue重载。

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();

答案 1 :(得分:3)

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

如果wheels_reference是非成员函数,您只需将其声明为:

wheels_reference(Bike &bike);

由于非const lvalue参数无法附加到临时值,因此您将无法调用wheels_reference(Bike())。由于wheels_reference是一个成员函数,因此您只需使用成员函数语法来说同样的事情:

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

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

答案 2 :(得分:1)

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

#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<Wheel, 2>中包装Wrap的所有API。

答案 3 :(得分:1)

你可以添加wheels_reference成员函数的几个cv-ref-qualified重载:

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

注意,如果object是临时的(&& case),那么你应该返回一个值(从数据成员复制构造,甚至更好地从它构造),也不应该引用。

完成所有四次超载后,您将涵盖所有可能的用例。