Segfault при использовании члена класса std :: function с оптимизацией компилятора - PullRequest
3 голосов
/ 28 марта 2019

У меня есть довольно простой пример кода, который вылетает при оптимизации с -O2 под gcc 8.2.0

#include <vector>
#include <functional>
#include <iostream>

template<typename T, typename Container>
class Lambda_Expression
{
    using Lambda = std::function<T()>;
    const Lambda & _lambda;

public:
    Lambda_Expression(const Lambda & l) : _lambda(l) {}

    T operator[](const std::size_t i)
    {
        std::cerr << "inside expression [] " << i << std::endl;
        return _lambda();
    }
};

auto lambda = []() -> double
{
    return 1.0;
};

int main()
{
    int N = 10;
    std::vector<double> res(N, 0.0);

    double x = lambda();

    std::cerr << "before for loop " << x << std::endl;

    auto test_expression = Lambda_Expression<double, std::vector<double>>(lambda);

    for( int idx=0; idx<N; ++idx )
    {
        std::cerr << "loop " << idx << std::endl;
        double x = test_expression[idx];
    }
}

Использование также -std=c++17, если это имеет значение.

Я получаю

before for loop 1
loop 0
inside expression [] 0
[1]    5288 segmentation fault  ./bench_lambdas

тогда как я ожидаю, что цикл будет выполняться в течение 10 итераций. Эта ошибка не отображается с уровнем оптимизации ниже 2.

Приведенный выше пример выглядит для меня как довольно безопасный код, и, насколько я знаю, оптимизации уровня 2 не должны нарушать правильный код.

Вопрос: Есть ли неопределенное поведение или неправильный код в моем примере, или в чем может быть проблема?

1 Ответ

6 голосов
/ 28 марта 2019

Насколько я знаю, это неопределенное поведение.

Проблема в том, что ваш класс регистрирует ссылку

// ..........V  reference !!!
const Lambda & _lambda;

аргумента конструктора

Lambda_Expression(const Lambda & l) : _lambda(l) {}

это std::function

using Lambda = std::function<T()>;

Но когда вы вызываете конструктор с помощью лямбды (как в main())

auto test_expression = Lambda_Expression<double, std::vector<double>>(lambda);

, вы сохраняете в _lambda ссылку навременный объект, потому что lambda не является std::function, поэтому он создает временный объект типа std::function<double()>, инициализированный с lambda.

Итак, проблема:ссылка на временный объект становится висячей ссылкой в ​​конце построения test_expression, поэтому при вызове test_expression[idx] вы используете _lambda, указывающий (потенциально) на мусор.

Я предлагаючтобы избежать подобных проблем, избегая ссылочной части (сделайте _lambda обычным членом типа std::function

const Lambda _lambda;  // <-- no more reference

, чтобы вы скопировали временный объект)

Но если вы действительно хотитечто _lambda является ссылкой на std::function, вы должны написать что-то следующим образом

std::function<double()>  f{lambda};

auto test_expression = Lambda_Expression<double, std::vector<double>>{f};

Таким образом, конструктор получает ссылку на std::function объект (f), который сохранился до его вызова.

...