Почему возврат константной ссылки из лямбды приводит к временному? - PullRequest
0 голосов
/ 04 мая 2018

У меня есть ситуация, когда у меня есть член, возвращающий const&, и затем этот результат пересылается в лямбду, которая имеет тот же тип возврата.

MSVC2017 идентифицирует эту ситуацию как рискованную и выдает предупреждение: returning address of local variable or temporary. Эмпирическое тестирование с использованием clang и других компиляторов показывает, что это действительно так. Я не понимаю, почему это отличается от нескольких вызовов методов, которые возвращают один и тот же тип.

Например, это прекрасно работает:

class A {
public:
    const std::string& name() const { return m_name; }
private:
    std::string m_name;
};

class B {
public:
    const std::string& name() const { return m_a.name(); }
private:
    A m_a;
};

//...
B b;
std::cout << b.name();

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

Но с лямбдой это не так:

class A {
public:
    const std::string& name() const { return m_name; }
private:
    std::string m_name;
};

//...
using Getter = std::function< const std::string&() >;
A a;
Getter g = [&a] { return a.name(); };
std::cout << g();

приводит к сбою или, по крайней мере, печати поврежденной памяти

Может кто-нибудь указать мне информацию о том, почему это не работает? Как правило, я ожидаю, что он будет работать так же ...

1 Ответ

0 голосов
/ 04 мая 2018

Тип возврата вашей лямбды - , а не ссылка. Это причина всех ваших проблем.

Ваша лямбда возвращает копию имени. Вы сохраняете эту лямбду в std::function, возвращающем const std::string&, что означает, что вы фактически вернете ссылку на эту копию, которая будет уничтожена, как только оператор вызова std::function вернёт! 1

Естественно, исправление состоит в том, чтобы изменить тип возврата лямбды:

Getter g = [&a]() -> const std::string& { return a.name(); };

// or
Getter g = [&a]() -> auto& { return a.name(); };
// or if you are feeling fancy :P
Getter g = [&a]() -> decltype(auto) { return a.name(); };

1 : Чтобы немного расширить это, вы можете представить реализацию std::function примерно так (только соответствующие части показаны и значительно упрощены):

template<typename R, typename... Ts>
struct function<R(Ts...)> {
  R operator()(Ts... Args) const {
    return callInternalFunctionObject(Args...); // here: copy will get destructed
  }
};
// R=const std::string&, and Ts is empty
...