我在使用 @R. Martinho Fernandes 的模板中有一些曲折的代码在可变参数模板中循环展开一些打包参数并在参数列表中的每个参数上调用相同代码的技巧。
但是,似乎好像 lambda 没有被正确初始化,而是在 functor(?) 实例之间共享变量,这似乎是错误的。
给定这段代码:
#include <iostream>
#include <functional>
template<typename... Args>
void foo(Args ... args) {
int * bar = new int();
*bar = 42;
using expand_type = int[];
expand_type{(
args([bar]() {
std::cerr<<std::hex;
std::cerr<<"&bar="<<(void*)&bar<<std::endl;
std::cerr<<" bar="<<(void*)bar<<std::endl;
std::cerr<<" bar="<<*bar<<std::endl<<std::endl;
}),
0) ...
};
};
int main() {
std::function<void(std::function<void()>)> clbk_func_invoker = [](std::function<void()> f) { f(); };
foo(clbk_func_invoker, clbk_func_invoker);
return 0;
}
我得到以下输出:
&bar=0x7ffd22a2b5b0
bar=0x971c20
bar=2a
&bar=0x7ffd22a2b5b0
bar=0
Segmentation fault (core dumped)
所以,我相信我看到的是两个仿函数实例共享捕获变量 bar
的相同地址,并且在调用第一个仿函数 bar
之后> 被设置为 nullptr
,然后当第二个仿函数 seg' 尝试取消引用 same bar
变量时(确切地说同一个地址)。
仅供引用,我意识到我可以通过将 [bar](){...
仿函数移动到变量 std::function
变量中来解决这个问题,并且然后捕获该变量。但是,我想了解 为什么 第二个仿函数实例使用完全相同的 bar
地址以及为什么它得到一个 nullptr
值。
我用 GNU 的 g++ 针对他们昨天检索和编译的主干版本运行了这个。
最佳答案
参数包其中包含 lambda 往往会让编译器适应。避免这种情况的一种方法是将扩展部分和 lambda 部分分开。
template<class F, class...Args>
auto for_each_arg( F&& f ) {
return [f=std::forward<F>(f)](auto&&...args){
using expand_type = int[];
(void)expand_type{0,(void(
f(decltype(args)(args))
),0)...};
};
}
这需要一个 lambda f
并返回一个对象,该对象将在其每个参数上调用 f
。
然后我们可以重写 foo
来使用它:
template<typename... Args>
void foo(Args ... args) {
int * bar = new int();
*bar = 42;
for_each_arg( [bar](auto&& f){
f( [bar]() {
std::cerr<<std::hex;
std::cerr<<"&bar="<<(void*)&bar<<std::endl;
std::cerr<<" bar="<<(void*)bar<<std::endl;
std::cerr<<" bar="<<*bar<<std::endl<<std::endl;
} );
} )
( std::forward<Args>(args)... );
}
我最初认为它与 std::function
构造函数有关。它不是。 A simpler example没有以相同方式崩溃的 std::function
:
template<std::size_t...Is>
void foo(std::index_sequence<Is...>) {
int * bar = new int();
*bar = 42;
using expand_type = int[];
expand_type{(
([bar]() {
std::cerr<<"bar="<<*bar<<'\n';
})(),
(int)Is) ...
};
}
int main() {
foo(std::make_index_sequence<2>{});
return 0;
}
we can invoke the segfault without the cerr
,使我们的反汇编更易于阅读:
void foo<3, 0ul, 1ul>(std::integer_sequence<unsigned long, 0ul, 1ul>)::{lambda()#1}::operator()() const:
pushq %rbp
movq %rsp, %rbp
movq %rdi, -8(%rbp)
movq -8(%rbp), %rax
movq (%rax), %rax
movl $3, (%rax)
nop
popq %rbp
ret
void foo<3, 0ul, 1ul>(std::integer_sequence<unsigned long, 0ul, 1ul>):
pushq %rbp
movq %rsp, %rbp
pushq %rbx
subq $40, %rsp
movl $4, %edi
call operator new(unsigned long)
movl $0, (%rax)
movq %rax, -24(%rbp)
movq -24(%rbp), %rax
movl $42, (%rax)
movq -24(%rbp), %rax
movq %rax, -48(%rbp)
leaq -48(%rbp), %rax
movq %rax, %rdi
call void foo<3, 0ul, 1ul>(std::integer_sequence<unsigned long, 0ul, 1ul>)::{lambda()#1}::operator()() const
movabsq $-4294967296, %rax
andq %rbx, %rax
movq %rax, %rbx
movq $0, -32(%rbp)
leaq -32(%rbp), %rax
movq %rax, %rdi
call void foo<3, 0ul, 1ul>(std::integer_sequence<unsigned long, 0ul, 1ul>)::{lambda()#1}::operator()() const
movl %ebx, %edx
movabsq $4294967296, %rax
orq %rdx, %rax
movq %rax, %rbx
nop
addq $40, %rsp
popq %rbx
popq %rbp
ret
我还没有解析反汇编,但在玩第一个时,它显然破坏了第二个 lambda 的状态。
关于C++ lambda 在模板的第二次扩展中没有捕获变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39128413/