编辑:
重写的答案,我已经给了这个问题适当的考虑这一次(即使我原来的答案是正确的)。
TL;博士
看在老天的份上,不要从函数或方法返回指向基于堆栈的变量的指针或引用,不管代码看起来有多花哨。本质上,这就是问题的全部。
让我们从一个测试程序开始,在我看来,它构成了
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()
返回元组中lambda的主体内
make_comparison_object
,所以让我们运行一些测试并查看不同值的结果
BEHAVIOUR
是的。
首先,行为=
使用元组
以下内容:
===== 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 =====
所以这是有效的,没有额外的副本(虽然有一些移动,但你需要这些)。
现在让我们试试行为=
向前使用标准
以下内容:
===== 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 =====
如你所见,这是一场灾难,当我们试图接近他们的时候,他们已经不在了。我们继续吧。
现在的行为=
使用标准参考
以下内容:
===比较开始=====
===构造元组=====
从2,3比较对象
构造函数(-3)
A损坏(-3)
从2,1比较对象
构造函数(-1)
A损坏(-1)
===检查/比较元组=====
左侧元组<0>=0,右侧元组<0>=0<=不正常
===比较结束,结果:0=====
同样的结果,一点也不奇怪。毕竟,我们返回了对堆栈上变量的引用。
最后,行为=
使用标准移动
是的。如你所见,结果和打电话一样
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
是的。
它所做的一切
使引用可复制。如果引用本身在您仍在使用包装器时消失,它仍然是皮肤下的一个悬挂指针,就像这里所做的那样。
正如我在开始时所说,这整件事可以归结为不返回指向堆栈上对象的指针(或引用)。只是裹在华丽的衣服里。
Live demo
是的。
更新
-更好地分析了这篇文章的原稿。
让我们看看操作人员到底在元组中放了什么:
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
来自传入lambda的参数,所以没关系
-
-foo.x
是原始类型,所以也可以
-
写了代码,
std::accumulate
返回一个
int
,所以我们又没事了
-
std::make_optional
构造一个临时的,所以
那是
不
安全的
所以,这段代码实际上并不安全,但并不是因为op状态的原因,@xskxzr的答案实际上没有任何贡献。一旦你想导出一个在lambda(或者任何其他类型的函数)中构造的非本原临时函数——不管用什么方法——你就必须正确地执行它,而且它曾经是这样的。
那是
我想说的是。