C++11 类的六大基本成员函数默认生成,default字段的含义

  1. 总览

    • 核心

      • 编译器会为一个类按需自动生成成员函数.

      • C++98:构造,析构,拷贝构造,拷贝赋值.

      • C++11:有了右值,就多了两个,移动构造和移动赋值.

    • 功能

      • 构造: 无参构造.T()

      • 析构: 调用成员变量析构.~T()

      • 拷贝构造: 同类型对象初始化.T a, b = a;

      • 拷贝赋值: 同类型对象赋值.T a,b; b = a;

      • 移动构造: 同类型右值初始化.T a = T();

      • 移动构造: 同类型右值赋值.T a; a = T();

    • 机制

      • 一般来说未定义,用到了,就添加.

      • 不过后面就有情况分析

    • C++98

      • 没有定义拷贝的任意一个,用到了都会构造.

    • C++11

      • 自定义了移动,赋值,析构.

      • 就隐式说明有资源操作,默认构造很可能无法满足,虽然也有可能满足,但是编译器就不冒险添加了.

      • 不过编译器也提供了很方便的机制,可以很轻易的表明需要生成默认.

      • =default即可.

      • 所以任意存在一个,其他一般的都不自动生成.除了析构这种必要的.

    • 生成类型

      • publicinline.

      • 析构函数则和父类一致.

    • 所有函数

      class T {
      public:
         T(){}
         ~T(){}
         T(const T&){}
         T& operator=(const T&){}
         T(T&&){}
         T& operator=(T&&){}
      };
      
      • 如上六种.

    • 生成

      • 采用std::move(T),最终匹配和类型实现有关.

  2. 生成

    • 按需生成

      • 没有用到就不会生成.

    • 生成拷贝构造

      [root@localhost test]# g++ test.cpp -std=c++11 -g
      [root@localhost test]# gdb ./a.out -q
      Reading symbols from /root/cppfile/test/a.out...done.
      (gdb) disassemble /r main
      Dump of assembler code for function main():
        0x00000000004007bd <+0>:     55      push   %rbp
        0x00000000004007be <+1>:     48 89 e5        mov    %rsp,%rbp
        0x00000000004007c1 <+4>:     53      push   %rbx
        0x00000000004007c2 <+5>:     48 83 ec 28     sub    $0x28,%rsp
        0x00000000004007c6 <+9>:     48 8d 45 e0     lea    -0x20(%rbp),%rax
        0x00000000004007ca <+13>:    48 89 c7        mov    %rax,%rdi
        0x00000000004007cd <+16>:    e8 6e 00 00 00  callq  0x400840 <T::T()>
        0x00000000004007d2 <+21>:    48 8d 45 d0     lea    -0x30(%rbp),%rax
        0x00000000004007d6 <+25>:    48 89 c7        mov    %rax,%rdi
        0x00000000004007d9 <+28>:    e8 62 00 00 00  callq  0x400840 <T::T()>
        0x00000000004007de <+33>:    48 8d 55 d0     lea    -0x30(%rbp),%rdx
        0x00000000004007e2 <+37>:    48 8d 45 e0     lea    -0x20(%rbp),%rax
        0x00000000004007e6 <+41>:    48 89 d6        mov    %rdx,%rsi
        0x00000000004007e9 <+44>:    48 89 c7        mov    %rax,%rdi
        0x00000000004007ec <+47>:    e8 d1 00 00 00  callq  0x4008c2 <T::operator=(T const&)>
        0x00000000004007f1 <+52>:    bb 00 00 00 00  mov    $0x0,%ebx
        0x00000000004007f6 <+57>:    48 8d 45 d0     lea    -0x30(%rbp),%rax
        0x00000000004007fa <+61>:    48 89 c7        mov    %rax,%rdi
        0x00000000004007fd <+64>:    e8 a2 00 00 00  callq  0x4008a4 <T::~T()>
        0x0000000000400802 <+69>:    48 8d 45 e0     lea    -0x20(%rbp),%rax
        0x0000000000400806 <+73>:    48 89 c7        mov    %rax,%rdi
        0x0000000000400809 <+76>:    e8 96 00 00 00  callq  0x4008a4 <T::~T()>
        0x000000000040080e <+81>:    89 d8   mov    %ebx,%eax
        0x0000000000400810 <+83>:    eb 26   jmp    0x400838 <main()+123>
        0x0000000000400812 <+85>:    48 89 c3        mov    %rax,%rbx
        0x0000000000400815 <+88>:    48 8d 45 d0     lea    -0x30(%rbp),%rax
        0x0000000000400819 <+92>:    48 89 c7        mov    %rax,%rdi
        0x000000000040081c <+95>:    e8 83 00 00 00  callq  0x4008a4 <T::~T()>
        0x0000000000400821 <+100>:   48 8d 45 e0     lea    -0x20(%rbp),%rax
        0x0000000000400825 <+104>:   48 89 c7        mov    %rax,%rdi
        0x0000000000400828 <+107>:   e8 77 00 00 00  callq  0x4008a4 <T::~T()>
        0x000000000040082d <+112>:   48 89 d8        mov    %rbx,%rax
        0x0000000000400830 <+115>:   48 89 c7        mov    %rax,%rdi
        0x0000000000400833 <+118>:   e8 78 fe ff ff  callq  0x4006b0 <_Unwind_Resume@plt>
        0x0000000000400838 <+123>:   48 83 c4 28     add    $0x28,%rsp
        0x000000000040083c <+127>:   5b      pop    %rbx
        0x000000000040083d <+128>:   5d      pop    %rbp
        0x000000000040083e <+129>:   c3      retq
      End of assembler dump.
      (gdb) break main
      Breakpoint 1 at 0x4007c6: file test.cpp, line 10.
      (gdb) r
      Starting program: /root/cppfile/test/./a.out
      
      Breakpoint 1, main () at test.cpp:10
      10          T a;
      (gdb) info source
      Current source file is test.cpp
      Compilation directory is /root/cppfile/test
      Located in /root/cppfile/test/test.cpp
      Contains 14 lines.
      Source language is c++.
      Compiled with DWARF 2 debugging format.
      Does not include preprocessor macro info.
      (gdb) !cat /root/cppfile/test/test.cpp
      #include<string>
      class T {
      public:
         int a;
         int b;
         std::string c{"122345"};
      };
      
      int main() {
         T a;
         T b;
         a = b;
         return 0;
      }
      
      
      • 上面的汇编可以看到.实现了一个赋值构造T::operator=(T const&).

      • 以及需要的构造和析构.

    • 默认拷贝构造

      [root@localhost test]# g++ test.cpp -std=c++11 -g
      [root@localhost test]# gdb ./a.out -q
      Reading symbols from /root/cppfile/test/a.out...done.
      (gdb) break main
      Breakpoint 1 at 0x4007c6: file test.cpp, line 10.
      (gdb) r
      Starting program: /root/cppfile/test/./a.out
      
      Breakpoint 1, main () at test.cpp:10
      10          T a;
      (gdb) disassemble main
      Dump of assembler code for function main():
        0x00000000004007bd <+0>:     push   %rbp
        0x00000000004007be <+1>:     mov    %rsp,%rbp
        0x00000000004007c1 <+4>:     push   %rbx
        0x00000000004007c2 <+5>:     sub    $0x28,%rsp
      => 0x00000000004007c6 <+9>:     lea    -0x20(%rbp),%rax
        0x00000000004007ca <+13>:    mov    %rax,%rdi
        0x00000000004007cd <+16>:    callq  0x400828 <T::T()>
        0x00000000004007d2 <+21>:    lea    -0x20(%rbp),%rdx
        0x00000000004007d6 <+25>:    lea    -0x30(%rbp),%rax
        0x00000000004007da <+29>:    mov    %rdx,%rsi
        0x00000000004007dd <+32>:    mov    %rax,%rdi
        0x00000000004007e0 <+35>:    callq  0x4008aa <T::T(T const&)>
        0x00000000004007e5 <+40>:    mov    $0x0,%ebx
        0x00000000004007ea <+45>:    lea    -0x30(%rbp),%rax
        0x00000000004007ee <+49>:    mov    %rax,%rdi
        0x00000000004007f1 <+52>:    callq  0x40088c <T::~T()>
        0x00000000004007f6 <+57>:    lea    -0x20(%rbp),%rax
        0x00000000004007fa <+61>:    mov    %rax,%rdi
        0x00000000004007fd <+64>:    callq  0x40088c <T::~T()>
        0x0000000000400802 <+69>:    mov    %ebx,%eax
        0x0000000000400804 <+71>:    jmp    0x400820 <main()+99>
        0x0000000000400806 <+73>:    mov    %rax,%rbx
        0x0000000000400809 <+76>:    lea    -0x20(%rbp),%rax
        0x000000000040080d <+80>:    mov    %rax,%rdi
        0x0000000000400810 <+83>:    callq  0x40088c <T::~T()>
        0x0000000000400815 <+88>:    mov    %rbx,%rax
        0x0000000000400818 <+91>:    mov    %rax,%rdi
        0x000000000040081b <+94>:    callq  0x4006c0 <_Unwind_Resume@plt>
        0x0000000000400820 <+99>:    add    $0x28,%rsp
        0x0000000000400824 <+103>:   pop    %rbx
        0x0000000000400825 <+104>:   pop    %rbp
        0x0000000000400826 <+105>:   retq
      End of assembler dump.
      (gdb) !cat test.cpp
      #include<string>
      class T {
      public:
         int a;
         int b;
         std::string c{"122345"};
      };
      
      int main() {
         T a;
         T b(a);
         return 0;
      }
      
      
      • 修改的只有一点点a=b改成b(a).

      • 可以看到这里调用的拷贝构造.T::T(T const&).

    • 反汇编

      • 通过objdump -d a.out可以查看_ZN1T的只有三个.

      • 分别对应,构造析构,拷贝构造.

      0000000000400828 <_ZN1TC1Ev>:
      000000000040088c <_ZN1TD1Ev>:
      00000000004008aa <_ZN1TC1ERKS_>:
      
      
      • _Z表示符号需要解密,1T,长度和符号.C1表示构造,Ev表示无参.

      • 具体可参考itaniumC++符号加密规则进行反向解密.

      • 最简单的就是c++filt指令获取结果.

      
      [root@localhost test]# c++filt _ZN1TC1Ev  _ZN1TD1Ev _ZN1TC1ERKS_
      T::T()
      T::~T()
      T::T(T const&)
      
      
    • 移动须知

      • 使用std::move,移动成员变量和父类.

      • 函数匹配,使用移动还是拷贝和匹配有关.

    • 智能移动

      [root@localhost test]# make
      g++ test.cpp -std=c++11 -g
      gdb ./a.out -q -ex 'break main' -ex 'r' -ex 'disassemble main' -ex 'c' -ex '!cat test.cpp'
      Reading symbols from /root/cppfile/test/a.out...done.
      Breakpoint 1 at 0x400a86: file test.cpp, line 27.
      Starting program: /root/cppfile/test/./a.out
      
      Breakpoint 1, main () at test.cpp:27
      27          T a;
      Dump of assembler code for function main():
        0x0000000000400a7d <+0>:     push   %rbp
        0x0000000000400a7e <+1>:     mov    %rsp,%rbp
        0x0000000000400a81 <+4>:     push   %rbx
        0x0000000000400a82 <+5>:     sub    $0x48,%rsp
      => 0x0000000000400a86 <+9>:     lea    -0x30(%rbp),%rax
        0x0000000000400a8a <+13>:    mov    %rax,%rdi
        0x0000000000400a8d <+16>:    callq  0x400b9e <T::T()>
        0x0000000000400a92 <+21>:    lea    -0x50(%rbp),%rax
        0x0000000000400a96 <+25>:    mov    %rax,%rdi
        0x0000000000400a99 <+28>:    callq  0x400b9e <T::T()>
        0x0000000000400a9e <+33>:    lea    -0x50(%rbp),%rax
        0x0000000000400aa2 <+37>:    mov    %rax,%rdi
        0x0000000000400aa5 <+40>:    callq  0x400c40 <std::move<T&>(T&)>
        0x0000000000400aaa <+45>:    mov    %rax,%rdx
        0x0000000000400aad <+48>:    lea    -0x30(%rbp),%rax
        0x0000000000400ab1 <+52>:    mov    %rdx,%rsi
        0x0000000000400ab4 <+55>:    mov    %rax,%rdi
        0x0000000000400ab7 <+58>:    callq  0x400c4e <T::operator=(T&&)>
        0x0000000000400abc <+63>:    mov    $0x0,%ebx
        0x0000000000400ac1 <+68>:    lea    -0x50(%rbp),%rax
        0x0000000000400ac5 <+72>:    mov    %rax,%rdi
        0x0000000000400ac8 <+75>:    callq  0x400c12 <T::~T()>
        0x0000000000400acd <+80>:    lea    -0x30(%rbp),%rax
        0x0000000000400ad1 <+84>:    mov    %rax,%rdi
        0x0000000000400ad4 <+87>:    callq  0x400c12 <T::~T()>
        0x0000000000400ad9 <+92>:    mov    %ebx,%eax
        0x0000000000400adb <+94>:    jmp    0x400b03 <main()+134>
        0x0000000000400add <+96>:    mov    %rax,%rbx
        0x0000000000400ae0 <+99>:    lea    -0x50(%rbp),%rax
        0x0000000000400ae4 <+103>:   mov    %rax,%rdi
        0x0000000000400ae7 <+106>:   callq  0x400c12 <T::~T()>
        0x0000000000400aec <+111>:   lea    -0x30(%rbp),%rax
        0x0000000000400af0 <+115>:   mov    %rax,%rdi
        0x0000000000400af3 <+118>:   callq  0x400c12 <T::~T()>
        0x0000000000400af8 <+123>:   mov    %rbx,%rax
        0x0000000000400afb <+126>:   mov    %rax,%rdi
        0x0000000000400afe <+129>:   callq  0x400980 <_Unwind_Resume@plt>
        0x0000000000400b03 <+134>:   add    $0x48,%rsp
        0x0000000000400b07 <+138>:   pop    %rbx
        0x0000000000400b08 <+139>:   pop    %rbp
        0x0000000000400b09 <+140>:   retq
      End of assembler dump.
      Continuing.
      copy assign
      [Inferior 1 (process 11575) exited normally]
      #include<string>
      #include<iostream>
      
      class D {
      public:
         D(){}
         ~D(){}
         D(D&&){}
         D(const D&){}
         D& operator=(const D&){
             std::cout << "copy assign" << std::endl;
         }
      };
      
      class T {
      public:
         int a;
         int b;
         std::string c{"122345"};
         D d;
         T(){}
         T(const T&){}
         T& operator=(T&&)=default;
      };
      
      int main() {
         T a;
         T b;
         a = std::move(b);
         return 0;
      }
      
      
      • 可以根据输出看到,使用的std::move,但是匹配的是拷贝赋值,因为没有实现移动赋值函数.

      • 这就是memberwise moves

      • 注意: 这里没有D没有自动生成移动赋值.

    • 智能移动并不都是移动,也有可能是拷贝.

  3. 影响

    • 互斥

      • 声明了移动的任意一个,不会生成另一个.

    • 原因

      • 声明了移动构造,说明默认的移动构造无法满足需求,可能有资源操作,编译器生成的也很可能满足不了.

      • 所以编译器就不自作主张的生成.

    • 来个案例

      #include<iostream>
      class T {
      public:
         T(){}
         ~T(){}
         T(T&&){}
      };
      
      int main() {
         T a,b;
         a = std::move(b);
      }
      
      • 编译报错,因为声明了移动构造,不会生成移动赋值.

    • copy影响移动move

      • 同理,默认copy无法满足,编译器认为copy涉及到资源操作,移动一个都不生成.

      • memberwise move可能用到copy.

    • move影响copy

      • 反过来也可以成立.

      • copy被声明为delete,也会参与匹配.

      • 默认move无法完成数据处理,那么默认copy很可能也不完成.

    • C++11move影响copy会影响到c++98的代码吗?

      • 不会,因为C++98没有移动语义.

      • 除非C++98的代码改成适配C++11的了,改了就应该都改.

  4. C++编译器演进

    • 三件套

      • 拷贝构造,拷贝赋值,析构.

      • 任意一个出现,一般都说明涉及到了资源分配.

      • 三者一般都是一起出现的.即定义一个,另外的也该定义.

    • C++98

      • 没有注意这茬,就允许出现了也可以自动生成.

      • C++11则改进了.

    • C++11

      • 理论上是三者一起出现的,声明一个,另外两个也该声明.

      • 声明析构还是会默认生成拷贝,因为不生成很多C++98代码会出问题.

      • 不过编译器给出了警告, 说在C++11中已经deprecated,虽然还会给你生成,但是建议自定义.

      • 析构和copy是共生的,那么间接的,有了析构就有了copy,有了copy就不会生成move.

    • 生成move的条件

      • copy

      • move

      • 无析构

    • 生成copy的条件

      • copy

      • move

      • 无析构

      • C++11为兼容C++98,允许生成copy,但是会警告.

  5. 使用默认

    • 核心

      • 有时候生成析构仅仅是为了输出.并不涉及到资源操作.

      • 那么自己实现就很麻烦.

      • 提供了=default,让编译器自动生成.

      • 省时省力.

    • 默认

      • 很多时候,默认都可以按照预期执行.

      • 有些时候声明=default可能多此一举,但是使得目的更明确,减小因为修改导致不再生成,导致编译报错的概率.

    • 小结

      • 默认构造:相同,无构造时.

      • 默认析构: 类似,c++11析构noexcept.

      • 拷贝构造: 行为一致,拷贝成员,没有才生成.c++11声明move,copy变成delete参与匹配.无拷贝赋值和析构时生成.

      • 拷贝赋值: 行为一致,拷贝成员,没有才生成.c++11声明move,copy变成delete参与匹配.无拷贝构造或析构时生成.

      • 移动构造:无拷贝,无移动,无析构定义,用时生成.智能移动,拷贝或移动.

      • 移动赋值:无拷贝,无移动,无析构定义,用时生成.智能移动,拷贝或赋值.

    • 成员模板

      • 模板不影响生成.因为编译器不考虑在内.

      • 在满足上面说的条件时,编译器仍然会生成,即特例化…而不是模板匹配.

      • 编译器默认>模板生成.

  6. 总结

    • 编译器按需生成,涉及资源操作就不尝试生成. 即自定义拷贝移动析构.

    • 默认移动是挨个std::move,具体类型的匹配函数可能是拷贝或移动.

    • 声明了move,copydelete,会参与匹配,然后报错.

    • 声明析构,也会生成copy,但是不推荐. 主要是向c++98兼容,建议用户自己实现.

    • 对拷贝移动进行模板化,不影响默认的函数生成.优先级高于模板.

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值