Segfault, если не указан тип возврата лямбда-функции - PullRequest
6 голосов
/ 18 июня 2019

У меня есть этот пример кода:

#include <functional>
#include <iostream>
#include <string>

void f(std::function<const std::string&()> fn) {
  std::cout << "in f" << std::endl;
  std::cout << "str: " << fn() << std::endl;
}

int main() {
  std::string str = "a";

  auto fn1 = [&]() { return str; };
  auto fn2 = [&]() { const std::string& str2 = str; return str2; };
  auto fn3 = [&]() -> const std::string& { return str; };

  std::cout << "in main" << std::endl;
  std::cout << "fn1: " << fn1() << std::endl;
  std::cout << "fn2: " << fn2() << std::endl;
  std::cout << "fn3: " << fn3() << std::endl;

  f(fn1);  // Segfaults
  f(fn2);  // Also segfaults
  f(fn3);  // Actually works

  return 0;
}

Когда я впервые написал это, я ожидал, что вызов fn1() внутри f() вернет ссылку на str в main. Учитывая, что str выделяется до тех пор, пока не вернется f(), это выглядело хорошо для меня. Но на самом деле происходит попытка получить доступ к возврату fn1() внутри f() segfaults.

То же самое происходит с fn2(), но что удивительно, fn3() работает правильно.

Учитывая, что fn3() работает, а fn1() нет, я что-то упускаю из-за того, как C ++ выводит возвращаемые значения лямбда-функций? Как это создаст этот сегмент?

Для записи, вот выходные данные, если я запускаю этот код:

звонит только f(fn3):

in main
fn1: a
fn2: a
fn3: a
in f
str: a

звонит только f(fn2):

in main
fn1: a
fn2: a
fn3: a
in f
Segmentation fault (core dumped)

звонит только f(fn1):

in main
fn1: a
fn2: a
fn3: a
in f
Segmentation fault (core dumped)

Ответы [ 2 ]

8 голосов
/ 18 июня 2019

Лямбда без конечного типа возврата, как в:

[&](){return str;};

Эквивалентно:

[&]()->auto{return str;};

Таким образом, эта лямбда возвращает копию строки.

Вызовобъект std::function приведет к следующему эквивалентному коду:

const string& std_function_call_operator(){
    // functor = [&]->auto{return str;};

    return functor();
    }

Когда эта функция вызывается, str копируется внутри временного объекта, ссылка привязывается к этому временному объекту, а затем временный объект уничтожается.Таким образом, вы получите знаменитый свисающий справочник.Это очень классический сценарий.

1 голос
/ 18 июня 2019

Изменен тип вычета лямбда N3638 .и теперь тип возврата lambda использует правила удержания типа возврата auto, которые лишают референтности.Следовательно, [&]() { return str;}; возвращает string.В результате в void f(std::function<const std::string&()> fn) вызов fn() возвращает висячую ссылку.Привязка ссылки к временному объекту продлевает срок службы временного объекта, но в этом случае привязка произошла глубоко внутри механизма std::function, поэтому к моменту возврата f() временное пространство уже исчезло.

Правило лямбда-вывода

В типах возврата auto и lambda используются несколько разные правила для определения типа результата из выражения.auto использует правила из 17.9.2.1 [temp.deduct.call], который явно отбрасывает cv-квалификацию верхнего уровня во всех случаях, тогда как лямбда-тип возврата основан на преобразовании lvalue-to-rvalue, которое отбрасывает cv-квалификациютолько для неклассных типов.В результате:

struct A { };

const A f();

auto a = f();               // decltype(a) is A
auto b = []{ return f(); }; // decltype(b()) is const A This seems like an unnecessary inconsistency.

Джон Спайсер:

Разница намеренная;auto предназначен только для предоставления константного типа, если вы явно запрашиваете его, а лямбда-тип возвращаемого значения обычно должен быть типом выражения.

Даниэль Крюглер:

Еще одно несоответствие: с autoиспользование braced-init-list может вывести специализацию std::initializer_list;. Было бы полезно, если бы это можно было сделать для лямбда-типа возврата.

Дополнительные примечания, февраль 2014 года:

EWG отметила, что g ++ и clang по-разному относятся к этому примеру, и передала его обратно CWG для разрешения.

Давайте посмотрим, что выводится в вашем коде :

fn1: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >

fn2: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >

fn3: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&

, поскольку вы можете видеть, что только последний фактически является const&

, вы можете проверить типы возврата ваших лямбд с помощью следующего кода:

//https://stackoverflow.com/a/20170989/10933809
#include <functional>
#include <iostream>
#include <string>

void f(std::function<const std::string&()> fn) {
  std::cout << "in f" << std::endl;
  std::cout << "str: " << fn() << std::endl;
}
#include <type_traits>
#include <typeinfo>
#ifndef _MSC_VER
#   include <cxxabi.h>
#endif
#include <memory>
#include <string>
#include <cstdlib>

template <class T>
std::string
type_name()
{
    typedef typename std::remove_reference<T>::type TR;
    std::unique_ptr<char, void(*)(void*)> own
           (
#ifndef _MSC_VER
                abi::__cxa_demangle(typeid(TR).name(), nullptr,
                                           nullptr, nullptr),
#else
                nullptr,
#endif
                std::free
           );
    std::string r = own != nullptr ? own.get() : typeid(TR).name();
    if (std::is_const<TR>::value)
        r += " const";
    if (std::is_volatile<TR>::value)
        r += " volatile";
    if (std::is_lvalue_reference<T>::value)
        r += "&";
    else if (std::is_rvalue_reference<T>::value)
        r += "&&";
    return r;
}
int main() {
  std::string str = "a";

  auto fn1 = [&]() { return str; };
  auto fn2 = [&]() { const std::string& str2 = str; return str2; };
  auto fn3 = [&]() -> const std::string& { return str; };

  std::cout << "in main" << std::endl;
  std::cout << "fn1: " << fn1() << std::endl;
  std::cout << "fn2: " << fn2() << std::endl;
  std::cout << "fn3: " << fn3() << std::endl;
auto f1=fn1();
  std::cout << "fn1: " << type_name<decltype(fn1())>() << std::endl;
  std::cout << "fn2: " << type_name<decltype(fn2())>() << std::endl;
  std::cout << "fn3: " << type_name<decltype(fn3())>() << std::endl;

  f(fn1);  // Segfaults
  f(fn2);  // Also segfaults
  f(fn3);  // Actually works

  return 0;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...