Effective C++的学习(一)(条款5-12)

 

条款05:了解C++默默编写并调用了那些函数

 

class empty{};                                                      //创建一个没有任何方法的空类

class empty{                                                          //编译器实际为你声明的类
public:    
    empty(){...}                                                      //默认构造函数
    empty(const empty& rhs){...}                             //拷贝构造函数
    ~empty(){...}                                                    //析构函数
    
    empty& operator=(const empty& rhs){...}            //拷贝赋值操作符
};

 

1.构造函数,析构函数,拷贝构造函数,拷贝赋值操作符是类最基本要实现的功能,如果你自己不写,编译器也会为你声明,且这些函数都是public inline函数。

2.编译器创建的构造函数,析构函数负责调用基类的,和非类专属成员的构造函数析构函数。

3.拷贝构造函数与拷贝赋值操作符单纯将每一个成员进行拷贝。

4.类中含不可更改的成员,编译器不会帮你写拷贝构造函数与拷贝赋值操作符,因为结果是不明确的。

条款06:若不想使用编译器自动生成的函数,就该明确拒绝

方法一.将对应成员函数声明为private并不予实现

1.原因:如果我们不想有基础方法,如果不写的话,编译器就会帮忙生成。

2.处理:在private中写只有声明,没有定义的方法。

 

3.原理:

①当编译器发现类中有声明,就不会再自己生成。

②因为这些方法在private中,又无法被直接调用。

③如果公共方法与友元强行使用,而这些方法并没有定义,编译器就会报出错误。

 

方法二.使用Uncopyable的基类

 

 

1.Uncopyable类:将拷贝的两种方法填入private中的类。

2.原理:其派生类要调用拷贝时,会去调用基类的方法,而基类却拒绝让其使用拷贝方法,报出错误。

条款07:为多态基类声明virtual析构函数

一.方法

 

1.原因:使用指向派生类的指针,但是以基类的方式去释放,导致派生类的析构函数未被调用,派生类的成分没有被销毁。

2.处理:给基类创建一个virtual析构函数。

3.原理:销毁时,可以调用合适的析构函数,从而避免局部销毁。

二.注意

 

1.如果不用于派生,不要给基类创建virtual方法。

  原因:virtual方法需要类给予额外的vptr(virtual table pointer)用于存储virtual函数表,导致内存小号变大。

  原理:每一个vptr指向一个vtbl(virtual table),编译器选择合适的函数,重写函数,实现多态。

2.不要将带有non-virtual析构函数的类当作基类(string,map,list...)

  原因:仍会出现局部销毁的风险

3. 析构函数调用过程

  从最深层派生类的析构函数开始调用,直到基类,基类的析构函数需要有定义(即使是纯虚函数)。

条款08:别让异常逃离析构函数 

1.析构函数绝对不要吐出异常。要在析构函数中捕捉异常,防止其传播造成不明确的行为。

2.如果客户需要对异常作出反应,应该自己定义异常捕捉函数,而不是在析构函数中完成。

 

条款09:绝不在构造和析构过程中调用virtual函数

 

1.原因:当你的派生类调用构造函数时,先调用基类构造函数,而基类构造函数调用的virtual函数,不会重写为派生类中的,也就是说,这时的virtual函数直接当成普通函数来用。

2.处理:派生的方法将参数传递到基类的方法中,参数应该是静态变量,避免出现未定义的情况。

条款10:令operator=返回一个reference to *this

1.为了能够实现连锁赋值,一般来说都要求如此,还包括了+=、-=、*=、/=等等。

 

条款11:在operator=中处理“自我赋值”

1.原因:在拷贝赋值过程中,想要进行资源管理,但是由于目的成员与源成员指向同一个,把源成员释放掉导致无法赋值成功。(注意:基类的指针或引用可以指向派生类)

class Widget{
...
private:
    Bitmap* pb;
};

Widget& Widget::operator=(const Widget& rhs)
{
    delete pb;                //所释放的恰好与rhs相同,则pb在接下来会指向已经被删除的对象
    pb = new Bitmap(*this.pb);
    return *this;
}

2.处理:

①证同:如果是同一个,直接返回,不同则采取资源管理的措施。

Widget& Widget::operator=(const Widget& rhs)
{
    if(this == &rhs) return *this;   //证同
    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;    
}

②上述仍存在如果new Bitmap时,产生异常,此时pb也会指向已经被删除的对象,所以我们在赋值之前不要释放原指针。

Widget& Widget::operator=(const Widget& rhs) 
{
    Bitmap* pOrig = pb;                  
    pb = new Bitmap(*rhs.pb);
    delete pOrig;                           //动态申请的仍然需要自己释放
    return *this;    
}

Widget& Widget::operator=(const Widget& rhs)
{
    Widget temp(rhs);             //局部变量会自己释放
    swap(temp);                   //将*this与temp交换
    return *this;    
}

条款12:复制对象时勿忘其每一个成分

1.由编译器自己生成的拷贝函数会把所有成员都进行拷贝

2.自己编写拷贝函数可能会漏掉,尤其是派生函数赋值时,千万不要忘记要在初始化列表中加上基类的构造函数。

3.拷贝构造函数与拷贝赋值符不能相互调用,如果嫌代码重复,可以重写一个private init()函数。

 

    

 

原文链接: https://www.cnblogs.com/sujian/p/12469296.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    Effective C++的学习(一)(条款5-12)

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/334915

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年3月1日 下午9:53
下一篇 2023年3月1日 下午9:53

相关推荐