在类segfaults中存储Lambda

时间:2015-05-26 14:13:19

标签: c++ lambda

这是我程序的简化版本,用于说明问题:

#include <functional>
#include <iostream>

template<class T>
class Piped {
public:
    typedef T value_type;
};

template<class P1, class P2, class T>
class PipeConnector : public Piped<T> {
public:
    PipeConnector(const P1 &p1, const P2 &p2)
     : m_1(p1), m_2(p2) { }

    bool run(const T &element) const {
        return m_1.run(element) || m_2.run(element);
    }
private:
    const P1 &m_1;
    const P2 &m_2;
};

template<class T>
class NullOp : public Piped<T> {
public:
    bool run(const T&) const {
        return false;
    }
};

template<class T, class Functor>
class FunctionOp : public Piped<T> {
public:
    FunctionOp(Functor f1)
     : m_1(f1) { }

    bool run(const T &element) const {
        return m_1(element);
    }
private:
    std::function<bool(T)> m_1;
};

template<class P1, class Functor>
auto operator|(const P1 &p1, const Functor &f2) {
    return PipeConnector<P1,
            FunctionOp<typename P1::value_type, std::function<bool(typename P1::value_type)>>, typename P1::value_type>(
                    p1, FunctionOp<typename P1::value_type, std::function<bool(typename P1::value_type)>>(f2));
}

int main() {
    auto p = NullOp<int>() | [](int x) -> bool { if (x < 10) { return true;} return false; };
    std::cout << p.run(20) << std::endl;
    return 0;
}

使用g ++ / clang ++ -std = c ++ 14编译此程序会导致段错误。向它添加-O3,使其在没有段错误的情况下运行。

当更改PipeConnector以不存储const引用但存储副本时,这是有效的。我认为问题是一些lambda范围问题,但我不明白出了什么问题。 -O3似乎在躲避这个问题?你能解释一下这个问题吗?

2 个答案:

答案 0 :(得分:1)

问题是NullOp<int>()是一个临时的,你将const引用存储到PipedConnector内。此临时具有完整表达式生存期,因此在p初始化后它不存在。当你打电话给p.run(20)时,你再次引用那个临时的,这个已被删除。由此产生的UB可能导致崩溃。

答案 1 :(得分:0)

您的operator|()创建了一个PipeConnector对象,其两个参数都是临时对象。然后PipeConnector构造函数将包含 references 的对象返回给这些临时对象。当调用该对象的run方法时,临时对象的生命周期已过期。

NullOp对象没有状态,并且(实际上)它的引用可能无关紧要,但是对于从lambda构造的std::function对象也是如此。这与lambda本身不同; lambda本身是无状态的,但std::function运算符包含一个函数指针。

我认为使用-O3编译器可以确定在调用std::function成员变量时应该调用哪个函数,因此它会内联调用。但即使看起来有效,它仍然是UB。

编辑:OP建议临时的生命周期应该延长,因为它绑定到引用。但是,在这种情况下,这不适用。见Does a const reference prolong the life of a temporary?

PipeConnector调用中创建的operator|()的构造函数中,有三个使用临时对象初始化的引用。其中第一个NullOp<int>()operator|()函数的参数,其生命周期明确没有通过§​​12.2[class.temporary]第5.1段的措辞扩展:

  

绑定到函数调用(5.2.2)中的引用参数的临时对象将持续存在,直到包含该调用的完整表达式完成。

另外两个临时对象是std::function<bool(int)>的一个实例,它来自lambda的转换,用于FunctionOp<int, std::function<bool(int)>>的初始化程序,以及构造的FunctionOp本身。在2013年和2014年上半年可用的草案标准版本中,此案例(与函数调用案例非常相似)具有类似的措辞:

  

构造函数的ctor-initializer(12.6.2 [class.base.init])中的引用成员的临时绑定将持续存在,直到构造函数退出。

但是,在DR 1696committed to the draft standard 29 September 2014)的提议决议中删除了该句子。我认为为清楚起见删除了这句话;据我所知,DR1696决议中的其他修改,特别是§12.6.2中新的第8段,将所提到的情况标记为无效:

  

绑定到mem-initializer中引用成员的临时表达式格式不正确。