为什么这段代码涉及使用对临时段错误的引用,尽管它似乎正确地管理了生命周期?
Why does this code involving use of references to temporaries segfault although it appears to properly manage lifetimes?
在试验免移动和免复制代码时,我写了以下内容:
#include <functional>
#include <type_traits>
#include <utility>
#define FWD(...) ::std::forward<decltype(__VA_ARGS__)>(__VA_ARGS__)
namespace {
template <typename Fn>
class wrapped_fn
{
public:
template <typename F>
explicit wrapped_fn(F&& fn)
: fn_{FWD(fn)}
{}
auto foo() &&
{
return wrapped_fn<Fn>{
FWD(fn_),
};
}
auto trigger_segfault() &&
{
return FWD(fn_)();
}
private:
Fn&& fn_;
};
template <typename F>
auto wrap(F&& f)
{
return ::wrapped_fn<F>{
FWD(f),
};
}
template <typename F>
auto frobnicate(F&& callable)
{
return ::wrap([&callable] {
return callable(); //
});
}
std::function<int()> call_me()
{
return [] { return 42; };
}
}
int main()
{
return ::frobnicate(call_me())
.foo()
.trigger_segfault();
}
我希望这段代码能够编译并正常工作(return 代码为 42
)。由于我只是维护对函数的引用,直到对 .trigger_function()
的调用和绑定到引用的临时对象对于完整表达式仍然有效(直到 ;
),所以我不应该有任何悬空引用或 copies/moves.
那么,为什么在使用 gcc 或 MSVC 编译时会出现此段错误?
通过使用 gdb,我确定在 .foo()
成员函数中对 wrapped_fn
的构造函数的调用是问题症状开始显现的地方。在构造函数调用的开始,我们有:
(gdb) p fn
= ((anonymous namespace)::<lambda()> &&) @0x7ffffffecdd0: {__callable = @0x7ffffffeced0}
(gdb) p fn.__callable
= (std::function<int()> &) @0x7ffffffeced0: {<std::_Maybe_unary_or_binary_function<int>> = {<No data fields>}, <std::_Function_base> = {static _M_max_size = 16,
static _M_max_align = 8, _M_functor = {_M_unused = {_M_object = 0x0, _M_const_object = 0x0, _M_function_pointer = 0x0, _M_member_pointer = NULL},
_M_pod_data = '[=11=]0' <repeats 15 times>},
_M_manager = 0x4ba0d2 <std::_Function_base::_Base_manager<(anonymous namespace)::call_me()::<lambda()> >::_M_manager(std::_Any_data &, const std::_Any_data &, std::
_Manager_operation)>}, _M_invoker = 0x4ba0b0 <std::_Function_handler<int(), (anonymous namespace)::call_me()::<lambda()> >::_M_invoke(const std::_Any_data &)>}
fn_
成员初始化后,我们有:
(gdb) p fn
= ((anonymous namespace)::<lambda()> &&) @0x7ffffffecdd0: {__callable = @0x7ffffffecdd0}
(gdb) p fn.__callable
= (std::function<int()> &) @0x7ffffffecdd0: {<std::_Maybe_unary_or_binary_function<int>> = {<No data fields>}, <std::_Function_base> = {static _M_max_size = 16,
static _M_max_align = 8, _M_functor = {_M_unused = {_M_object = 0x7ffffffecdd0, _M_const_object = 0x7ffffffecdd0, _M_function_pointer = 0x7ffffffecdd0,
_M_member_pointer = (void (std::_Undefined_class::*)(std::_Undefined_class * const)) 0x7ffffffecdd0, this adjustment -8574455321466846208},
_M_pod_data = "<garbage data>"}, _M_manager = 0x7ffffffecf10}, _M_invoker = 0x4b9be6 <main()+83>}
__callable
成员已更改。我不明白为什么,因为 fn_
和 fn
都是引用,而 FWD(fn)
应该只是保留 fn
类别的转换。 fn
绝对不会被复制或移动,因为我捕获了一个变量,该变量计算对特殊成员函数的调用并且计数没有递增。
frobnicate()
中的 lambda 函数一直存在,直到 frobnicate()
返回。它仅被引用,并且将事物绑定到引用通常不会使对象保持活动状态。也就是说,从 frobnicate()
返回的对象引用了一个已销毁的对象,触摸它会导致未定义的行为。
引用使对象保持活动状态的情况是将临时对象立即绑定到本地引用。即使这样,也假设临时文件没有以任何形式隐藏。例如,将临时包装成任何形式的调用时都不起作用。
在试验免移动和免复制代码时,我写了以下内容:
#include <functional>
#include <type_traits>
#include <utility>
#define FWD(...) ::std::forward<decltype(__VA_ARGS__)>(__VA_ARGS__)
namespace {
template <typename Fn>
class wrapped_fn
{
public:
template <typename F>
explicit wrapped_fn(F&& fn)
: fn_{FWD(fn)}
{}
auto foo() &&
{
return wrapped_fn<Fn>{
FWD(fn_),
};
}
auto trigger_segfault() &&
{
return FWD(fn_)();
}
private:
Fn&& fn_;
};
template <typename F>
auto wrap(F&& f)
{
return ::wrapped_fn<F>{
FWD(f),
};
}
template <typename F>
auto frobnicate(F&& callable)
{
return ::wrap([&callable] {
return callable(); //
});
}
std::function<int()> call_me()
{
return [] { return 42; };
}
}
int main()
{
return ::frobnicate(call_me())
.foo()
.trigger_segfault();
}
我希望这段代码能够编译并正常工作(return 代码为 42
)。由于我只是维护对函数的引用,直到对 .trigger_function()
的调用和绑定到引用的临时对象对于完整表达式仍然有效(直到 ;
),所以我不应该有任何悬空引用或 copies/moves.
那么,为什么在使用 gcc 或 MSVC 编译时会出现此段错误?
通过使用 gdb,我确定在 .foo()
成员函数中对 wrapped_fn
的构造函数的调用是问题症状开始显现的地方。在构造函数调用的开始,我们有:
(gdb) p fn
= ((anonymous namespace)::<lambda()> &&) @0x7ffffffecdd0: {__callable = @0x7ffffffeced0}
(gdb) p fn.__callable
= (std::function<int()> &) @0x7ffffffeced0: {<std::_Maybe_unary_or_binary_function<int>> = {<No data fields>}, <std::_Function_base> = {static _M_max_size = 16,
static _M_max_align = 8, _M_functor = {_M_unused = {_M_object = 0x0, _M_const_object = 0x0, _M_function_pointer = 0x0, _M_member_pointer = NULL},
_M_pod_data = '[=11=]0' <repeats 15 times>},
_M_manager = 0x4ba0d2 <std::_Function_base::_Base_manager<(anonymous namespace)::call_me()::<lambda()> >::_M_manager(std::_Any_data &, const std::_Any_data &, std::
_Manager_operation)>}, _M_invoker = 0x4ba0b0 <std::_Function_handler<int(), (anonymous namespace)::call_me()::<lambda()> >::_M_invoke(const std::_Any_data &)>}
fn_
成员初始化后,我们有:
(gdb) p fn
= ((anonymous namespace)::<lambda()> &&) @0x7ffffffecdd0: {__callable = @0x7ffffffecdd0}
(gdb) p fn.__callable
= (std::function<int()> &) @0x7ffffffecdd0: {<std::_Maybe_unary_or_binary_function<int>> = {<No data fields>}, <std::_Function_base> = {static _M_max_size = 16,
static _M_max_align = 8, _M_functor = {_M_unused = {_M_object = 0x7ffffffecdd0, _M_const_object = 0x7ffffffecdd0, _M_function_pointer = 0x7ffffffecdd0,
_M_member_pointer = (void (std::_Undefined_class::*)(std::_Undefined_class * const)) 0x7ffffffecdd0, this adjustment -8574455321466846208},
_M_pod_data = "<garbage data>"}, _M_manager = 0x7ffffffecf10}, _M_invoker = 0x4b9be6 <main()+83>}
__callable
成员已更改。我不明白为什么,因为 fn_
和 fn
都是引用,而 FWD(fn)
应该只是保留 fn
类别的转换。 fn
绝对不会被复制或移动,因为我捕获了一个变量,该变量计算对特殊成员函数的调用并且计数没有递增。
frobnicate()
中的 lambda 函数一直存在,直到 frobnicate()
返回。它仅被引用,并且将事物绑定到引用通常不会使对象保持活动状态。也就是说,从 frobnicate()
返回的对象引用了一个已销毁的对象,触摸它会导致未定义的行为。
引用使对象保持活动状态的情况是将临时对象立即绑定到本地引用。即使这样,也假设临时文件没有以任何形式隐藏。例如,将临时包装成任何形式的调用时都不起作用。