• 【 C++11 】类的新功能


    目录

    1、准备条件

    2、默认成员函数

    3、类成员变量初始化

    4、强制生成默认函数的关键字default

    5、禁止生成默认函数的关键字delete

    6、继承和多态中的final与override关键字


    1、准备条件

    下面的测试案例需用用到移动构造和移动赋值的测试代码,我们给出上篇博文封装过移动构造和移动赋值的简化版string类放到下面,以方便后续的测试代码中进行调用观察现象:

    1. namespace cpp
    2. {
    3. class string
    4. {
    5. public:
    6. // 构造函数
    7. string(const char* str = "")
    8. :_size(strlen(str))
    9. , _capacity(_size)
    10. {
    11. //cout << "string(char* str)" << endl;
    12. _str = new char[_capacity + 1];
    13. strcpy(_str, str);
    14. }
    15. // 交换两个对象的数据
    16. void swap(string& s)
    17. {
    18. ::swap(_str, s._str);
    19. ::swap(_size, s._size);
    20. ::swap(_capacity, s._capacity);
    21. }
    22. // 拷贝构造
    23. string(const string& s)
    24. :_str(nullptr)
    25. {
    26. cout << "string(const string& s) -- 深拷贝" << endl;
    27. string tmp(s._str);
    28. swap(tmp);
    29. }
    30. // 赋值重载
    31. string& operator=(const string& s)
    32. {
    33. cout << "string& operator=(string s) -- 深拷贝" << endl;
    34. string tmp(s);
    35. swap(tmp);
    36. return *this;
    37. }
    38. // 移动构造
    39. string(string&& s)
    40. :_str(nullptr)
    41. , _size(0)
    42. , _capacity(0)
    43. {
    44. cout << "string(string&& s) -- 移动构造" << endl;
    45. swap(s);
    46. }
    47. // 移动赋值
    48. string& operator=(string&& s)
    49. {
    50. cout << "string& operator=(string&& s) -- 移动赋值" << endl;
    51. swap(s);
    52. return *this;
    53. }
    54. // 析构函数
    55. ~string()
    56. {
    57. delete[] _str;
    58. _str = nullptr;
    59. }
    60. private:
    61. char* _str;
    62. size_t _size;
    63. size_t _capacity; // 不包含最后做标识的\0
    64. };
    65. }

    2、默认成员函数

    原来C++类中(C++11之前),有6个默认成员函数:

    1. 构造函数
    2. 析构函数
    3. 拷贝构造函数
    4. 拷贝赋值重载
    5. 取地址重载
    6. const 取地址重载

    最重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。C++11 新增了两个:移动构造函数和移动赋值运算符重载。针对移动构造和移动赋值,编译器也会默认生成,不过生成的条件极其苛刻,下面展开来讨论:

    ①、移动构造:

    • 如果你没有自己实现移动构造函数,且均没有实现析构函数拷贝构造拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。

    ②、移动赋值:

    • 如果你没有自己实现移动赋值重载函数,且均没有实现析构函数 拷贝构造拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)

    ③、总结:

    • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

    ④、示例:

    • 对于如下的Person类,我们不需要写析构、拷贝构造、赋值重载,因为Person的成员变量_name是自定义类型,会自动去调用string类的拷贝构造、析构、赋值重载完成深拷贝。而内置类型_age完成值拷贝即可。
    1. // 以下代码在vs2013中不能体现,在vs2019及以上的编译器下才能演示体现上面的特性。
    2. class Person
    3. {
    4. public:
    5. Person(const char* name = "", int age = 0)
    6. :_name(name)
    7. , _age(age)
    8. {}
    9. /*Person(const Person& p)
    10. :_name(p._name)
    11. , _age(p._age)
    12. {}*/
    13. /*Person& operator=(const Person& p)
    14. {
    15. if (this != &p)
    16. {
    17. _name = p._name;
    18. _age = p._age;
    19. }
    20. return *this;
    21. }*/
    22. /*~Person()
    23. {}*/
    24. private:
    25. cpp::string _name;
    26. int _age;
    27. };
    28. int main()
    29. {
    30. Person s1;
    31. Person s2 = s1;//拷贝构造
    32. Person s3 = std::move(s1);//移动构造
    33. Person s4;
    34. s4 = std::move(s2);//移动赋值
    35. return 0;
    36. }

    因为我们都没写拷贝构造、析构、赋值,所以编译器会默认生成移动构造和移动赋值运算符重载。针对main函数的测试用例,很明显,s2 = s1是拷贝构造,下面的两个分别调用移动构造和移动赋值:

    但凡我把Person类中的任何一个拷贝构造或析构或赋值放出来,结果都是去调用string类的拷贝构造函数去完成深拷贝:


    3、类成员变量初始化

    默认生成的构造函数,对于自定义类型会自动调用它的构造函数进行初始化,对于内置类型并不会进行处理,于是C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化,这个我们在类和对象默认就讲了,这里再简要提下。

    1. class Person
    2. {
    3. public:
    4. //……
    5. private:
    6. //C++11允许非静态成员变量在声明时进行初始化赋值
    7. cpp::string _name = "王五";
    8. int _age = 19;
    9. static int _num;//静态成员变量不能给缺省值
    10. };

    4、强制生成默认函数的关键字default

    C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定生成移动构造或移动赋值

    • 示例:如下我们实现了拷贝构造,所以编译器就不会生成移动构造和移动赋值了,会去调用自定义类型string类的拷贝构造函数完成深拷贝:
    1. class Person
    2. {
    3. public:
    4. //构造函数
    5. Person(const char* name = "", int age = 0)
    6. :_name(name)
    7. , _age(age)
    8. {}
    9. //拷贝构造
    10. Person(const Person& p)
    11. :_name(p._name)
    12. , _age(p._age)
    13. {}
    14. private:
    15. cpp::string _name;
    16. int _age;
    17. };
    18. int main()
    19. {
    20. Person s1;
    21. Person s2 = s1;
    22. Person s3 = std::move(s1);
    23. Person s4;
    24. s4 = std::move(s2);
    25. return 0;
    26. }

    为了让编译器生成移动构造和移动赋值,我们可以使用default关键字显示指定生成移动构造或移动赋值:

    1. class Person
    2. {
    3. public:
    4. //构造函数
    5. Person(const char* name = "", int age = 0)
    6. :_name(name)
    7. , _age(age)
    8. {}
    9. //拷贝构造
    10. Person(const Person& p)
    11. :_name(p._name)
    12. , _age(p._age)
    13. {}
    14. //强制生成移动构造
    15. Person(Person&& pp) = default;
    16. //强制生成移动赋值
    17. Person& operator=(Person&& pp) = default;
    18. private:
    19. cpp::string _name;
    20. int _age;
    21. };


    5、禁止生成默认函数的关键字delete

    在C++中,如果想禁止生成默认成员函数,我们有如下两种方式:

    1. 在C++98中,是该函数设置成private,并且只是声明不定义,这样只要其他人想要调用就会报错。
    2. 在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数

    示例:我不想让一个类被拷贝,那么在拷贝构造声明的后面加上=delete即可:


    6、继承和多态中的final与override关键字

    • 这个我们在继承和多态章节已经进行了详细讲解,这里再强调下:

    1、final:修饰虚函数,表示该虚函数不能再被重写,修饰类表示不能被继承。

    • 这里我父类的虚函数Drive不想被其它人重写,在其后面加上final即可,此时子类就无法对Drive进行重写了,如下:

    final修饰一个类,让其不能被继承,如下:

    2、override:

    • override的作用是检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

  • 相关阅读:
    excel 批量翻译-excel 批量函数公司翻译大全免费
    面试官:如何优雅依赖多个版本的jar包?
    Java基础
    Netty优化-扩展自定义协议中的序列化算法
    Netty服务端开发及性能优化
    第六节:Word中对象的层次结构
    ESMM论文精读
    安装fabricmanager解决print(torch.cuda.is_available())报错NumCudaDevices()
    Flow公链 |FCL1.0正式上线
    免费的国产数据集成平台推荐
  • 原文地址:https://blog.csdn.net/bit_zyx/article/details/127330888