当作为 Function 的参数传递给线程时,为什么仿函数的 dtor 调用两次(多次)?
Why dtors of functors called twice (multitimes), when passed to a thread as the argument for Function?
由于以下示例,我有这个问题:
#include <utility>
#include <thread>
#include <iostream>
typedef struct foo{
foo() = default;
void operator()(int i) const {}
~foo(){std::cout<<"dtor\n";}
} foo;
typedef struct bar{
bar() = default;
bar(const bar&) {std::cout<<"copy\n";}
bar(bar&&) {std::cout<<"move\n";}
void operator()(const foo& f, int k) const {f(k);}
~bar(){std::cout<<"bar\n";}
} bar;
int main(){
foo f_1, f_2;
bar b_1, b_2;
int i(0), j(0);
while(i++!=2){
std::thread t(b_1, std::cref(f_1), i);
b_2(f_2, j);
t.join();
}
int dummy(0);
std::cin >> dummy;
}
哪个产生(gcc 和 clang 给出相同的结果)
copy
move
bar
bar
copy
move
bar
bar
0
bar
bar
dtor
dtor
,其中 0 是用户输入。
所以 bar 的 dtor——Function 的参数——在胎面完成其工作后被调用两次(每次迭代)。我不明白的是,为什么两次而不是一次(用于制作副本)?
此外,是否可以避免复制,以防仿函数本身拥有不可复制的资源或复制成本高昂?
谢谢!
更新
它不一定是原始问题的两倍,请参阅下面的 Praetorian 的回答,其中涉及 3 个 dtor 调用和 2 个移动。
您将左值 (b_1
) 传递给 std::thread
构造函数,因此它会复制该参数。我修改了您的示例,以便更容易理解正在发生的事情。请注意,while
条件已更改为仅执行一次。
typedef struct foo{
foo() = default;
void operator()(int i) const {}
// ~foo(){std::cout<<"dtor\n";}
} foo;
typedef struct bar{
bar() {std::cout<<"bar " << this << '\n';}
bar(const bar&) {std::cout<<"bar copy " << this << '\n';}
bar(bar&&) {std::cout<<"bar move " << this << '\n';}
void operator()(const foo& f, int k) const {f(k);}
~bar() {std::cout<<"~bar " << this << '\n';}
} bar;
int main(){
foo f_1, f_2;
bar b_1, b_2;
int i(0), j(0);
while(i++!=1){
std::cout << "---- 1 ----\n";
std::thread t(b_1, std::cref(f_1), i);
std::cout << "---- 2 ----\n";
b_2(f_2, j);
t.join();
std::cout << "---- 3 ----\n";
}
}
在 gcc this produces the output(注释是我的)
bar 0x7fffbcc2156c // b_1 constructed
bar 0x7fffbcc2156d // b_2 constructed
---- 1 ----
bar copy 0x7fffbcc21580 // std::thread ctor makes copy of b_1
bar move 0x162a038 // that copy is moved by std::thread impl
~bar 0x7fffbcc21580 // moved from object is destructed
---- 2 ----
~bar 0x162a038 // bar object owned by thread instance is destroyed
---- 3 ----
~bar 0x7fffbcc2156d // b_2 is destroyed
~bar 0x7fffbcc2156c // b_1 is destroyed
如果您想避免复制,有几个不同的选择。在将 b_1
实例传递给 std::thread
.
之前,您可以将其包装在 std::reference_wrapper
中
std::thread t(std::cref(b_1), std::cref(f_1), i);
或者您可以允许 std::thread
构造函数移动 b_1
实例。
std::thread t(std::move(b_1), std::cref(f_1), i);
Live demo。在这种情况下,您将招致由 std::thread
实现执行的内部移动构造。
由于以下示例,我有这个问题:
#include <utility>
#include <thread>
#include <iostream>
typedef struct foo{
foo() = default;
void operator()(int i) const {}
~foo(){std::cout<<"dtor\n";}
} foo;
typedef struct bar{
bar() = default;
bar(const bar&) {std::cout<<"copy\n";}
bar(bar&&) {std::cout<<"move\n";}
void operator()(const foo& f, int k) const {f(k);}
~bar(){std::cout<<"bar\n";}
} bar;
int main(){
foo f_1, f_2;
bar b_1, b_2;
int i(0), j(0);
while(i++!=2){
std::thread t(b_1, std::cref(f_1), i);
b_2(f_2, j);
t.join();
}
int dummy(0);
std::cin >> dummy;
}
哪个产生(gcc 和 clang 给出相同的结果)
copy
move
bar
bar
copy
move
bar
bar
0
bar
bar
dtor
dtor
,其中 0 是用户输入。
所以 bar 的 dtor——Function 的参数——在胎面完成其工作后被调用两次(每次迭代)。我不明白的是,为什么两次而不是一次(用于制作副本)?
此外,是否可以避免复制,以防仿函数本身拥有不可复制的资源或复制成本高昂?
谢谢!
更新 它不一定是原始问题的两倍,请参阅下面的 Praetorian 的回答,其中涉及 3 个 dtor 调用和 2 个移动。
您将左值 (b_1
) 传递给 std::thread
构造函数,因此它会复制该参数。我修改了您的示例,以便更容易理解正在发生的事情。请注意,while
条件已更改为仅执行一次。
typedef struct foo{
foo() = default;
void operator()(int i) const {}
// ~foo(){std::cout<<"dtor\n";}
} foo;
typedef struct bar{
bar() {std::cout<<"bar " << this << '\n';}
bar(const bar&) {std::cout<<"bar copy " << this << '\n';}
bar(bar&&) {std::cout<<"bar move " << this << '\n';}
void operator()(const foo& f, int k) const {f(k);}
~bar() {std::cout<<"~bar " << this << '\n';}
} bar;
int main(){
foo f_1, f_2;
bar b_1, b_2;
int i(0), j(0);
while(i++!=1){
std::cout << "---- 1 ----\n";
std::thread t(b_1, std::cref(f_1), i);
std::cout << "---- 2 ----\n";
b_2(f_2, j);
t.join();
std::cout << "---- 3 ----\n";
}
}
在 gcc this produces the output(注释是我的)
bar 0x7fffbcc2156c // b_1 constructed
bar 0x7fffbcc2156d // b_2 constructed
---- 1 ----
bar copy 0x7fffbcc21580 // std::thread ctor makes copy of b_1
bar move 0x162a038 // that copy is moved by std::thread impl
~bar 0x7fffbcc21580 // moved from object is destructed
---- 2 ----
~bar 0x162a038 // bar object owned by thread instance is destroyed
---- 3 ----
~bar 0x7fffbcc2156d // b_2 is destroyed
~bar 0x7fffbcc2156c // b_1 is destroyed
如果您想避免复制,有几个不同的选择。在将 b_1
实例传递给 std::thread
.
std::reference_wrapper
中
std::thread t(std::cref(b_1), std::cref(f_1), i);
或者您可以允许 std::thread
构造函数移动 b_1
实例。
std::thread t(std::move(b_1), std::cref(f_1), i);
Live demo。在这种情况下,您将招致由 std::thread
实现执行的内部移动构造。