zhngs

zhngs

c++注解(4)——现代c++类

一.类组成
1.this指针
2.成员函数
3.权限
4.成员变量
二.类特种函数规则
1.c++98的失误
2.大三律
3.c++11中特种函数生成规则
4.最佳实践
三.传统c++面向对象
1.继承
2.虚函数
四.现代面向对象
五.对象语义和数据语义

一.类组成

我认为c++的类由四部分组成:

  • 成员函数

  • 成员变量

  • 权限

  • this指针

写类一定要明确一个核心思想:一切的操作都以成员变量为核心,成员函数和权限还有this指针只是c++的语法糖而已

1.this指针

  • this指针,指向对象的内存地址

  • this指针参与成员函数的重载决议,一般作为第一个成员函数的第一个参数

  • 想要将this指针智能指针化,可以public继承std::shared_from_this

2.成员函数

  • 成员函数的第一个参数应该是Self* this,如果成员函数是const属性的,那么第一个参数可以理解为const Self* this,这也是为什么const对象无法调用非const成员函数的原因

  • static的成员函数没有this指针

3.权限

  • public用来暴露接口,private和protected都可以隐藏成员变量和成员函数

  • private和protected的区别是在继承方面,如果不使用继承,private和protected没有区别,如果使用继承,子类无法访问父类private成员,但是可以访问protected成员

4.成员变量

  • 成员变量是我认为是类的核心所在,其他的东西都是为成员变量服务

二.类特种函数规则

c++11中有6大特殊函数,构造函数,析构函数,拷贝构造函数,拷贝赋值函数,移动构造函数,移动赋值函数

1.c++98的失误

  • 当一个类中存在析构函数时,证明类中有可能存在指向堆中的指针,需要使用析构函数来释放,这是标准的RAII手法,理论上来说,这种情况下不应该存在默认的拷贝构造函数和拷贝赋值函数,因为可能会造成double free的情况,但是事实情况c++98允许其存在,这是一个不得不说的失误

2.大三律

  • 大三律:析构函数,拷贝构造函数,拷贝赋值函数,你只要声明了其中一个,就必须声明另外两个,原因和c++98失误的原因一样

  • 大三律没有在c++98中得到重视,是一件痛心的事

3.c++11中特种函数生成规则

  • 默认构造函数:当类中不包含用户声明的构造函数时会生成

  • 默认析构函数:当类中不包含用户声明的析构函数时会生成

  • 默认拷贝构造函数:当类中不包含用户声明的拷贝构造函数时会生成,如果类中声明了移动操作,则默认拷贝构造函数会被删除

  • 默认拷贝赋值函数:当类中不包含用户声明的拷贝赋值函数时会生成,如果类中声明了移动操作,则默认拷贝赋值函数会被删除

  • 默认移动构造和移动赋值:当类中不包含用户声明的拷贝操作,移动操作,析构函数时才会出现

4.最佳实践

  • 对于默认特种函数要心里有数,对于非数据语义的类,应该使用delete关键字默认删掉,这也是boost库中noncopyable存在的意义,不过我更建议生动删掉
class Test { public: Test() ~Test(); Test(const Test&) = delete; Test& operator=(const Test&) = delete; };

三.传统c++面向对象

传统的c++面向对象的核心思想是:public继承+虚函数实现的多态

1.继承

  • c++支持三种权限的继承,但实际上只有public继承最有用

  • 继承不推荐使用,耦合强度仅次于友元

  • 多继承更加不提倡,简直是噩梦

2.虚函数

  • 虚函数多态的本质是在类的内存布局中有一个虚函数表,并且是按照偏移而不是名字来执行相应函数的

  • 虚函数的二进制兼容性差,因为是按照偏移来寻址,所以增加或删除虚函数会破坏二进制兼容性

  • 虚函数不推荐使用,除非你能保证这个抽象会是一直正确,不然应该避免使用

  • c++11中使用override关键字,可以避免派生类中忘记重写虚函数的错误

  • 如果使用public继承+虚函数多态,一定要将析构函数写成虚函数的形式,不然有可能导致内存泄漏

四.现代面向对象

  • 传统的面向对象的写法思维是,我写一个类,类里面有一个虚函数,你继承我的类,重写该虚函数

  • 现代的面向对象思维是,我写一个类,类里面设置一个std::function,提供一个注册接口,你使用的时候只需要将lambda或者普通函数注册到给std::function即可

两者达到的效果是一样的,都可以实现多态的效果,但是现代的面向对象,少了继承那种叠床架屋的写法,所有的类更加平坦,灵活性也更高

五.对象语义和数据语义

类可以分为对象语义和数据语义

  • 数据语义:将数据封装到类内,允许拷贝,例如string,vector都是数据语义的类

  • 对象语义:不应该允许拷贝的类,比如你封装一个文件描述符,拷贝一次没有意义,这是因为文件描述符从使用上来说应该是对象语义的

对于对象语义来说,应该默认删掉拷贝运算,对于数据语义来说,可以重载相关运算符,如加减乘除