V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
frankmdong
V2EX  ›  C++

请教下关于拷贝构造函数的问题

  •  
  •   frankmdong · 2022-07-20 00:53:22 +08:00 · 1761 次点击
    这是一个创建于 857 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近在看 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

    菜鸟问题有点多,不好意思

    8 条回复    2022-07-21 00:44:29 +08:00
    pocarisweat
        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 一次。
    pocarisweat
        2
    pocarisweat  
       2022-07-20 01:19:42 +08:00
    @pocarisweat
    不过这里是因为 Data 的复制构造函数有副作用,所以按照语言标准必须调用两次。如果没有副作用,编译器后端很容易(但不保证)将其优化掉。这有点类似物理学的不确定性原理🐶
    frankmdong
        3
    frankmdong  
    OP
       2022-07-20 11:07:13 +08:00
    @pocarisweat #2 #2 感谢回复!
    “Data 的复制构造函数有副作用” 中的副作用是指我自定义了复制构造函数吗?
    在 godbolt 里面还有一处是

    DataHolder wrapper;
    std::vector<Data> myDatas{11}; // 11 是隐式构造 Data{11}
    wrapper.setDatas(myDatas);

    这样写的话,std::initializer_list 那里又变成只有一次 Copy constructor 了,感觉就是...很迷...
    cnbatch
        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>
    frankmdong
        5
    frankmdong  
    OP
       2022-07-20 14:23:23 +08:00
    @cnbatch #4 #4 感谢回复!这个塞入 std::initializer_list{} 的区别是不是就是左值和右值的区别? std::vector<Data> myDatas{Data{11}},也只会打印一次 Copy constructor 。 而一开始传的是个 data 变量,这是个左值,所以会复制多一遍。
    cnbatch
        6
    cnbatch  
       2022-07-20 14:42:30 +08:00
    @frankmdong 没错,就是左值和右值的缘故
    pocarisweat
        7
    pocarisweat  
       2022-07-21 00:03:57 +08:00
    @frankmdong
    这里的副作用指的是调用了外部函数来 print ,如果没有这个外部调用,编译器能很容易知道它啥也没做然后就能优化掉。
    frankmdong
        8
    frankmdong  
    OP
       2022-07-21 00:44:29 +08:00
    @pocarisweat #7 #7 懂啦,看来老哥只在凌晨刷 v 站
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2675 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 33ms · UTC 11:55 · PVG 19:55 · LAX 03:55 · JFK 06:55
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.