条款03 const
- const 与作用域:
- global作用域中的常量
- local作用域中的static对象
- class内部的static或nonstatic成员变量
- const 修饰指针或指针所指的对象: const 类型 * const 指针名
- const 修饰迭代器: 迭代器类似一个指针 T*
- const 的 iterator : 类似 T* const 指针名 ,const 修饰的是迭代器这个指针,即 迭代器不可变,但迭代器所指的对象可以改变
- const_iterator: 类似 const T* 指针名,const修饰的是迭代器所指的对象,迭代器可变,但迭代器所指的对象不可变
- const 与 函数声明
- const 修饰返回值:具有安全性和高效性,提高容错率。比如为了防止 (a*b)=c 这个语句出现,我们在operator* 成员函数的返回值char& 前加上const,使得返回值不可被改变,即不能被赋值,即不能出现在赋值号左边。 (为何在返回类型后加引用& ? 因为如果返回的是内置类型(非引用),改变她的值是不合法的。纵使合法,通过by-value传递的对象,是我们期望传递的那个对象的一个副本,并非对象其本身)
- const 修饰参数:参考 条款20 尽量以pass-by-reference-to-const 替换 pass-by-value
- const 修饰成员函数:
- 重要性:
- 使class的接口更明显,即容易得知哪个成员函数能够改变成员变量,哪个不可以
- 操作const成员对象:我们可以通过pass-by-reference-to-const传递const对象,从而提高程序效率,cosnt成员函数可以操作传递而来的const对象
- 两个constness
- bitwise constness 认为const成员函数不可以改变任何non-static成员变量(漏网之鱼:如果成员变量是指针呢?使用const成员函数操作const指针,指针值确实不变,但是指针所指的值却可以改变,不符合const成员函数的意义)
- logical constness 认为const成员函数可以改变成员变量的某些bit,但是不能被编译器发现。所谓做人留一线,日后好相见。就是不要在成员函数中公然赋值给成员变量,这就过分了,编译器不会让你通过的。
- 如果希望在const成员函数中操作non-const成员变量?将non-const成员变量使用mutable修饰
- const与non-const成员函数的重载
- 两个是不同的函数,支持重载,但为了避免重复代码过多,一般使用non-const成员函数调用const成员函数,当然在调用之前使用static_cast将类型转换为const T&,调用完之后使用const_cast消除const T&的常量性
- 重要性:
条款04 使用对象前确保初始化
- class内的初始化:
- 尽量使用member initialization list初始化:成员变量是 内置类型 const 或者 reference (先初始化menber initialization list内的成员变量或不在list内但是有default constructor 的成员变量,再执行构造函数本体的语句)(使用member initialization list初始化是直接调用copy构造函数,而在构造函数本体内赋值是先用default构造函数初始化,而后使用copy assignment运算符,可以明显看到显然用member initialization list效率高一些)
- 初始化次序:(总结自 《探索c++对象模型》)
- 首先判断如果有virtual base class,有多个则按照从左到右,从深到浅的次序初始化
- 使用member initialization list 初始化virtual base class,如果有参数需要传递则必须传递
- 不在list中,但virtual base class有default constructor,则使用default constructor初始化
- class中每一个virtual base class subobject 的偏移量offset,必须在执行期可被存取
- 然后判断如果有nonvirtual base class,有多个则按照base class的声明次序初始化
- 使用member initialization list 初始化base class,如果有参数需要传递则必须传递
- 不在list中,但base class有default constructor,则使用default constructor初始化
- 当初始化第二个及后继的base class时,this指针需要调整
- 初始化每一个vptr,将其指向适当的virtual function table
- 初始化member成员变量,有多个则按照member声明次序初始
- 使用member initialization list 初始化member,与list的顺序无关,还是按照member声明次序初始化
- 不在list中,但member有default constructor,则使用default constructor初始化
- 首先判断如果有virtual base class,有多个则按照从左到右,从深到浅的次序初始化
- static对象的初始化次序
- non-static: stack和heap-based
- static:
- local static:函数内声明为static的对象
- non-local static: 在global范围内,在namespace作用域内,在class内,在file文件内的static对象(不同编译单元内定义的non-local static对象初始化次序不明确,很可能会出现调用未初始化的static对象的操作。为了防止这种操作,我们可以将non-local static对象转为local static对象。具体是在一个专属函数中声明该static对象,然后该函数返回一个该static对象的引用,当我们需要使用该static对象时直接调用该函数。由于local static的初始化次序是有明确定义的,即”在函数调用期间“”并且首次遇上static对象的定义“时初始化,当我们调用该专属函数并得到static对象的引用时,可确保该static对象是已被初始化的。)
条款05 06 10 11 copy assignment
- 当成员变量有const 或者 reference时,想要支持赋值操作,必须自己定义copy assignment
- 想要class不支持赋值操作,也为了防止编译器自己合成一个copy assignment ,可以将copy assignment设置为private
- operator = 函数
- 返回值是reference-to-*this ,即return *this
- 防止自我赋值,可在函数头加上 判断语句 if(this==&rhs)
- 注意语句顺序:最好在分配好一个完整的新内存之后,再删除旧指针所指的对象
- 使用swap-and-copy技术
- 对于参数const T& rhs,创建一个rhs的副本temp,并swap *this和temp的数据,最后*this的数据是rhs中的数据(副本),temp中的数据是旧数据,再return *this之前自动销毁。
- 技巧:可以使用by-value传实参,因为by-value方式会产生T rhs的一个副本,与上条的temp的作用相同。
条款07 在多态基类中声明virtual析构函数
- 对于一种情况: Base* name = new Derived() 即一个base class 指针指向一个derived class 对象,当我们delete name删除该指针所指对象时:
- 如果Base类的析构函数是non-virtual,即不支持多态,那么只删除了base class部分,derived class成分未删除,即造成了局部删除的困境
- 如果Base类的析构函数是virtual,即支持多态(《探索c++对象模型》中多态的主要用途:多态是经由共同接口来影响类型的封装,这个接口通常定义在base class抽象类里,接口由virtual机制引发,直到执行期时才能知道对象的真正类型(这个例子是Derived class类),并执行哪个函数实体(这个例子是Derived class 的 virtual 析构函数实体)) ,说明她会删除整个对象,包括derived class成分。
- pure virtual 析构函数的应用:如果你希望一个base class是抽象类,但是这个类中没有其他可用的virtual函数了,你可以设置base class 的析构函数为pure virtual destructor
- 抽象类一般为base class,即我们声明的这个类是基类,她会派生其他类,参考上条我们可以将这个base class的析构函数声明为virtual
- pure 可提供抽象类,即声明了pure之后这个class 无实体
- 值得注意的一点:如果你在base class 声明了pure virtual 析构函数,我们必须定义这个析构函数,如Base::~Base(){}(因为析构函数的运作方式是:在derived class的析构函数中静态调用base class 的析构函数,如果我们没有提供那个定义那么链接会断,编译器会出错的)
条款08 不要在构造函数和析构函数中调用virtual函数
- 在base class构造和析构函数中调用的virtual函数不可下降至derived class阶层,即base class 调用的virtual函数是base class版本的virtual函数实体,derived class 调用的virtual函数是derived class 版本的virtual函数实体。
- 纵使base class构造和析构函数中调用的virtual函数可以下降至derived class阶层,在执行derived class构造函数初始化derived成员变量之前,会首先执行base class的构造函数,这个构造函数调用的virtual函数如果是derived class版本的函数实体,由于derived class的virtual函数几乎必须调用其成员变量(此时还未被初始化),所以base class 的构造函数会调用到derived class 还未初始化的成员变量,这样非常不安全。
- 如果base class构造和析构函数必须使用derived class中的信息,可以将之前调用的virtual变成non-virtual,并且在derived class初始化成员队列里面将base class构造函数所需的信息当作参数传递进去,既可以实现将derived class中的信息上升至base class阶层。如果要传递的信息是derived class 还未被初始化的成员变量,可参照条款04,将这个成员变量封装在static专属函数中,她无需derived class对象构建完成,就可以先初始化,然后被我们使用。
条款13 auto_ptr和shared_ptr
- auto_ptr 智慧指针
- 当控制流离开作用域之时,auto_ptr自动调用delete删除所指的对象
- 两个auto_ptr不能指向同一个对象,如果两个指针销毁的话会delete两次对象,因为这个原因,auto_ptr的copy构造函数和赋值操作都具有破坏性和异常,对于 两个auto_ptr对象 ap1 = ap2,会删除ap1中的对象,然后将ap2中的对象赋值给ap1,ap2指向null。
- shared_ptr 引用计数型智慧指针(RCSP)
- std::trl::stared_ptr
- copy构造函数和赋值函数都正常
条款20 尽量以pass-by-reference-to-const 替换 pass-by-value
- 以pass-by-value方式传递对象至函数,会调用copy构造函数以实参值初始化一个复件,在函数返回前调用析构函数删除这个复件,调用端返回值获得的函数返回值也是一个复件。所以by-value方式会调用多个copy构造函数和析构函数。
- by-reference-to-const方式传递的好处?
- 可以避免对象分割(slicing)问题。当一个derived class对象以by-value方式传递至函数,并视为一个base class对象时,会调用base class的copy构造函数,则derived class成分会被切割掉,只留下一个base class对象。而如果使用pass-by-reference方式传递,则会调用真实类型(即derived class类型)的copy构造函数,避免对象分割问题。
- 不会有任何copy构造函数和析构函数被调用。
- 声明const可以保证函数内不改变传入的实参。对于by-value方式来说,为了保护传入的实参,函数内部不会对传入的参数做任何改变,只会对传入参数的一个副本做改变。对于by-reference方式来说,声明一个const,可以保证传入的实参不做改变。
条款27 转型
- const_cast 常量性消除
- dynamic_cast 安全向下转型
- reinterpret_cast 低级转型
- static_cast 强迫隐式转型