最近在看 Professional C++, Fifth Edition 入门,看到 rule of five 和移动构造函数的时候写了个示例测了下,发现一个奇怪的问题。
为什么 std::vector<Data> myDatas{data};
这行会调两次 Data 的拷贝构造函数呢?是复制初始化导致的吗?
如果我改成 std::vector<Data> myDatas;
然后 push_back(data)
, 这里就只会打印一次 "Copy constructor"。
另外:C++ 一般这种问题应该怎么分析?一般是看汇编还是断点?
int main() {
DataHolder wrapper;
Data data{11}; // Normal constructor
std::vector<Data> myDatas{data};// Copy constructor * 2
wrapper.setDatas(myDatas); // Copy constructor
}
全代码在:https://godbolt.org/z/EE5vnG3Kz
菜鸟问题有点多,不好意思
1
pocarisweat 2022-07-20 01:16:22 +08:00 1
https://stackoverflow.com/questions/20501638/stdvector-init-with-braces-call-copy-constructor-twice
因为 std::vector { data } 实际上调用的是 std::initializer_list 版本的构造函数,所以从 data 到 initializer_list copy 了一次,而从 initializer_list 到 vector 又 copy 了一次。如果写成 std::vector { std::move(data) } 会发现 move 了一次 copy 了一次。 如果你写成 std::vector(1, data) 就会只 copy 一次。 |
2
pocarisweat 2022-07-20 01:19:42 +08:00
@pocarisweat
不过这里是因为 Data 的复制构造函数有副作用,所以按照语言标准必须调用两次。如果没有副作用,编译器后端很容易(但不保证)将其优化掉。这有点类似物理学的不确定性原理🐶 |
3
frankmdong OP @pocarisweat #2 #2 感谢回复!
“Data 的复制构造函数有副作用” 中的副作用是指我自定义了复制构造函数吗? 在 godbolt 里面还有一处是 DataHolder wrapper; std::vector<Data> myDatas{11}; // 11 是隐式构造 Data{11} wrapper.setDatas(myDatas); 这样写的话,std::initializer_list 那里又变成只有一次 Copy constructor 了,感觉就是...很迷... |
4
cnbatch 2022-07-20 14:10:35 +08:00 1
也许这样描述会清楚一点
std::vector<Data> myDatas { data } 第一次 Copy constructor:data“塞入”std::initializer_list{} 第二次 Copy constructor:从 std::initializer_list{} “变成” std::vector<Data> std::vector<Data> myDatas{11} 第一次 Copy constructor:11“塞入”std::initializer_list{}。数字 11 此时不是 class Data ,仅仅是简单的基本类型复制,跟那个 class 暂时还没关联,并不会用到你在 class Data 提供的 Copy constructor 。 第二次 Copy constructor:从 std::initializer_list{} “变成” std::vector<Data> |
5
frankmdong OP @cnbatch #4 #4 感谢回复!这个塞入 std::initializer_list{} 的区别是不是就是左值和右值的区别? std::vector<Data> myDatas{Data{11}},也只会打印一次 Copy constructor 。 而一开始传的是个 data 变量,这是个左值,所以会复制多一遍。
|
6
cnbatch 2022-07-20 14:42:30 +08:00
@frankmdong 没错,就是左值和右值的缘故
|
7
pocarisweat 2022-07-21 00:03:57 +08:00
@frankmdong
这里的副作用指的是调用了外部函数来 print ,如果没有这个外部调用,编译器能很容易知道它啥也没做然后就能优化掉。 |
8
frankmdong OP @pocarisweat #7 #7 懂啦,看来老哥只在凌晨刷 v 站
|