Редактировать: Переписанный ответ, на этот раз я рассмотрел проблему должным образом (хотя мой первоначальный ответ был правильным).
tl; dr Ради всего святого, не возвращайте указатель или ссылку на переменную на основе стека из функции или метода, как бы странно ни выглядел код. В этом, собственно, весь этот вопрос.
Давайте начнем с тестовой программы, которая, на мой взгляд, составляет MCVE :
#include <iostream>
#include <functional>
#include <tuple>
#define USE_MAKE_TUPLE 0
#define USE_STD_FORWARD 2
#define USE_STD_REF 3
#define USE_STD_MOVE 4
#define BEHAVIOR USE_STD_MOVE
struct A {
A(int data) : data(data) { std::cout << "A constructor (" << data << ")\n"; }
A(const A& other) : data(other.data) { std::cout << "A copy constructor (" << data << ")\n"; }
A(A&& other) : data(other.data) { std::cout << "A move constructor (" << data << ")\n"; }
A(const A&& other) : data(other.data) { std::cout << "A const move constructor (" << data << ")\n"; }
~A() { std::cout << "A destroyed (" << data << ")\n"; data = 999; }
friend bool operator<(const A& lhs, const A& rhs) {
return lhs.data < rhs.data;
}
int data;
};
struct Foo {
Foo(A a1, A a2) : a1(std::move(a1)), a2(std::move(a2)) {}
A a1;
A a2;
friend bool operator< (const Foo& lhs, const Foo& rhs)
{
auto make_comparison_object = [](const Foo& foo)
{
std::cout << "make_comparison_object from " << foo.a1.data << ", " << foo.a2.data << "\n";
#if BEHAVIOR == USE_MAKE_TUPLE
return std::make_tuple (make_A (foo), 42);
#elif BEHAVIOR == USE_STD_FORWARD
return std::forward_as_tuple (make_A (foo), 42);
#elif BEHAVIOR == USE_STD_REF
A a = make_a (foo);
return std::make_tuple (std::ref (a), 42);
#elif BEHAVIOR == USE_STD_MOVE
return std::make_tuple (std::move (make_A (foo)), 42);
#endif
};
std::cout << "===== constructing tuples =====\n";
auto lhs_tuple = make_comparison_object (lhs);
auto rhs_tuple = make_comparison_object (rhs);
std::cout << "===== checking / comparing tuples =====\n";
std::cout << "lhs_tuple<0>=" << std::get <0> (lhs_tuple).data << ", rhs_tuple<0>=" << std::get <0> (rhs_tuple).data << "\n";
return lhs_tuple < rhs_tuple;
}
static A make_A (const Foo& foo) { return A (-foo.a2.data); }
};
int main() {
Foo foo1(A{2}, A{3});
Foo foo2(A{2}, A{1});
std::cout << "===== comparison start =====\n";
auto result = foo1 < foo2;
std::cout << "===== comparison end, result: " << result << " =====\n";
}
Теперь проблема явно заключается в захвате временного объекта, созданного путем вызова make_A()
в теле лямбда-выражения в кортеже, возвращаемом make_comparison_object
, поэтому давайте запустим несколько тестов и посмотрим на результаты для других значений BEHAVIOUR
.
Сначала, ПОВЕДЕНИЕ = USE_MAKE_TUPLE :
===== constructing tuples =====
make_comparison_object from 2, 3
A constructor (-3)
A move constructor (-3)
A destroyed (-3)
make_comparison_object from 2, 1
A constructor (-1)
A move constructor (-1)
A destroyed (-1)
===== checking / comparing tuples =====
lhs_tuple<0>=-3, rhs_tuple<0>=-1 <= OK
A destroyed (-1)
A destroyed (-3)
===== comparison end, result: 1 =====
Так что это сработало, и не было лишних копий (хотя было несколько ходов, но тогда они нужны).
Теперь давайте попробуем BEHAVIOR = USE_STD_FORWARD :
===== comparison start =====
===== constructing tuples =====
make_comparison_object from 2, 3
A constructor (-3)
A destroyed (-3)
make_comparison_object from 2, 1
A constructor (-1)
A destroyed (-1)
===== checking / comparing tuples =====
lhs_tuple<0>=0, rhs_tuple<0>=0 <= Not OK
===== comparison end, result: 0 =====
Это, как вы можете видеть, катастрофа, временные ресурсы ушли к тому времени, когда мы пытаемся получить к ним доступ. Давайте двигаться дальше.
Теперь ПОВЕДЕНИЕ = USE_STD_REF :
===== comparison start =====
===== constructing tuples =====
make_comparison_object from 2, 3
A constructor (-3)
A destroyed (-3)
make_comparison_object from 2, 1
A constructor (-1)
A destroyed (-1)
===== checking / comparing tuples =====
lhs_tuple<0>=0, rhs_tuple<0>=0 <= Not OK
===== comparison end, result: 0 =====
Тот же результат, который меня совсем не удивляет. В конце концов, мы вернули ссылку на переменную в стеке.
И, наконец, ПОВЕДЕНИЕ = USE_STD_MOVE . Как видите, результаты аналогичны простому вызову std::make_tuple
без перемещения - как вы могли ожидать при построении объекта из временного объекта:
===== constructing tuples =====
make_comparison_object from 2, 3
A constructor (-3)
A move constructor (-3)
A destroyed (-3)
make_comparison_object from 2, 1
A constructor (-1)
A move constructor (-1)
A destroyed (-1)
===== checking / comparing tuples =====
lhs_tuple<0>=-3, rhs_tuple<0>=-1 <= OK
A destroyed (-1)
A destroyed (-3)
Итак, для подведения итогов, просто используйте std_make_tuple
, как я писал ранее.
Обратите внимание, что вы должны быть очень осторожны с std::ref
. Все, что он делает - это делает ссылку на копию. Это еще свисающий указатель под скином, если сама ссылка исчезнет, пока вы все еще используете оболочку, как здесь.
И, как я сказал в начале, все сводится к тому, что мы не возвращаем указатель (или ссылку) на объект в стеке. Это просто завернутые в модную одежду.
Живая демоверсия .
Обновление - лучший анализ оригинального поста ОП.
Давайте посмотрим, что на самом деле ОП помещает в свой кортеж:
auto make_comparison_object = [](const Foo& foo) {
return std::forward_as_tuple(
foo.s,
-foo.x,
std::accumulate(
foo.z.begin(),
foo.z.end(),
0),
foo.p ? std::make_optional(*foo.p) : std::nullopt);
Итак, что он там вкладывает? Ну:
foo.s
исходит из параметра, переданного в лямбду, так что все в порядке
-foo.x
- это примитивный тип, так что это тоже нормально
- с кодом, как написано,
std::accumulate
вернуть int
, так что снова мы в порядке
std::make_optional
создает временное, поэтому это , а не безопасно
Таким образом, этот код на самом деле небезопасен, но не по той причине, что указывает OP, и ответ @ xskxzr фактически ничего не дает. Как только вы захотите экспортировать не примитивный временный объект, созданный внутри лямбда-функции (или вообще любой другой тип функции) - какими бы то ни было средствами - вы должны сделать это правильно, и так было всегда. Это , что я пытаюсь донести до нас.