Effective C++ 阅读笔记之Chapter2

前言

大家好,我是雨墨,我深知好记性不如烂笔头的道理,所以在阅读的时候都尽量写读书笔记,方便日后复习,当然笔记并不能代替书籍上的内容。希望我的笔记也能帮助到大家,如果我的笔记有什么问题,欢迎大家给小老弟纠错~

条款五总结

  • 当一个空类被创建对象的时候,编译器会为其声明一个默认的构造函数、拷贝构造函数、析构函数、拷贝赋值;
  • 只有当类没有声明这些函数的时候,编译器才会主动做这些事,但是做的事情可能和你想象中的有些不一样;
  • 如果想在一个含有 reference 成员的 class 中支持赋值操作,你必须自己定义 copy assignment 操作符,因为 C++ 并不支持让 reference 改指向不同对象。同样的如果类内含 const 对象,或是 base class 定义的 copy assignment 是 delete 的,derived class 也无法使用 copy assignment

条款六总结

  • 如果不想使用编译器生成的函数,那么就显示地将其声明在 private 中并不去定义他们,或是利用 delete 也可以达到这一个目的;

  • 如果是不想使用编译器 derived class 生成的函数,那么就在 base class 中将其定义到 private 中

    class Uncopyable {
        protected:
        	Uncopyable() {}
        	~Uncopyable() {}
        private:
        	Uncopyable(const Uncopyable&);
        	Uncopyable& operator= (const Uncopyable&);
    };
    
    class HomeForSale : private Uncopyable {
        ...
    };
    

条款七总结

  • 有 derived class 对象经由 base class 指针删除,如果 base class 中带有一个 non-virtual 析构函数,那么结果一定是未定义的,实际运行的结果是 derived 成分未删除,只是删除了 base 成分。

  • 任何 class 只要带有 virtual 函数几乎都带有一个 virtual 析构函数,当 class 不企图被当作 base class ,令其析构函数为 virtual 往往是个馊主意,因为想要实现一个 virtual 函数,对象需要携带一个指向虚函数表的指针(vptr),指向一块虚函数表(vtbl),每一个带有 virtual 函数的 class 都有一个对应的 vtbl。这会增大对象的体积。

  • 详细讲讲虚函数表,

    • 编译器在发现基类中有虚函数时,会自动为每个含有虚函数的类生成一份虚表,该表是一个一维数组,虚表里保存了虚函数的入口地址
    • 编译器会在每个对象的前四个字节中保存一个虚表指针,即 vptr ,指向对象所属类的虚表。在构造时,根据对象的类型去初始化虚指针 vptr ,从而让 vptr 指向正确的虚表,从而在调用虚函数时,能找到正确的函数
    • 所谓的合适时机,在派生类定义对象时,程序运行会自动调用构造函数,在构造函数中创建虚表并对虚表初始化。在构造子类对象时,会先调用父类的构造函数,此时,编译器只“看到了”父类,并为父类对象初始化虚表指针,令它指向父类的虚表;当调用子类的构造函数时,为子类对象初始化虚表指针,令它指向子类的虚表
    • 当派生类对基类的虚函数没有重写时,派生类的虚表指针指向的是基类的虚表;当派生类对基类的虚函数重写时,派生类的虚表指针指向的是自身的虚表;当派生类中有自己的虚函数时,在自己的虚表中将此虚函数地址添加在后面。
  • 抽象 class 一般被当作 base class 使用,因此为其声明一个 pure virtual 析构函数。然而,你必须为其定义

    class AWOV {
        public:
        	virtual ~AWOV() = 0;
    };
    
    AWOV::~AWOV() {}
    

条款八总结

  • 不要让析构函数抛出异常,如果析构函数中发生了异常,那么可能会造成内存泄漏的问题。因此, C++ 并不喜欢析构函数中抛出异常,为什么不喜欢?C++ Primer 异常处理章节中提到,出于栈展开可能使用析构函数的考虑,析构函数不该抛出不能被它自身处理的异常。
  • 如果在析构函数中必须执行一个动作,且这个动作可能会抛出异常,那要么是异常中断退出,要么是吞下这个异常,但你应该为用户留一手检查,即提供一个函数执行此操作而非在析构函数中,如果用户都不关心这件事情,那么我们也无能为力。

条款九总结

  • 不要在构造函数或析构函数中调用 virtual 函数,如果被调用的函数是 pure virtual 函数的话,编译器会马上报出错误信息,但如果调用的函数是 virtual 函数且在类中已经有了一份实现代码,程序会兴高采烈的向后执行,留给你一个百思不得其解的问题,为何我这个结果不是我预期中的呢?不是调用的是 derived class 对应版本的函数吗?原因是,在构造 derived class 对象的时候,会先调用 base class 的构造函数,此时对象内的 derived class 成分并未被初始化,最安全的做法就是视其不存在,所以对象在 derived class 构造函数之前都不会被视为 derived class 对象,所以调用的 vritual 函数自然是 base class 版本。同样的道理也适用于析构函数,一旦 derived class 析构函数被执行,对象值的 derived class 成员变量就呈现未定义值,此时 C++ 视其仿佛不存在。
  • 那如何确定继承体系上的对象被创建之后,会调用那个函数的适当的版本,一个解决方法就是不将其声明为 virtual ,而是在每次 derived class 构造函数的时候向 base class 构造函数传递一个表示本 class 的参数信息。

条款十总结

令 opreator= 返回指向 *this 的 reference。

条款十一总结

  • 必须使得 operator= 能够处理自赋值的情况,但是在处理自赋值的情况的时候也要兼顾代码的证同测试和异常处理,让 operator= 具备“异常安全性”往往会自动获得“自我复制安全”的回报。

    class Bitmap {...};
    class Widget {
      ...
      private:
        Bitmap* pb;
    };
    
    Widget& Widget::operator= (const Widget& rhs) {
        Bitmap* pOrig = pb;
        pb = new Bitmap(*rhs.pb);	// 如果这一步出现了异常,那么 pOrig 也没有被 delete
        delete pOrig;
        return *this;
    }
    

    如果很关心效率,可以把证同测试 if (this == &rhs) 放在函数起始处,但这样做的前提是你觉得“自我复制”的频率很高。

  • 还有一种方法也能保证代码“异常安全”且“自我赋值安全”,那就是大名鼎鼎的 copy and swap 技术。

    class Widget {
        ...
        void swap(Widget&);
        ...
    };
    
    Widget& Widget::operator= (const Widget& rhs) {
        Widget tmp(rhs);
        swap(tmp);
        return *this;
    }
    

条款十二总结

  • 如果你的类的构造函数是你显示声明的,那么请你在修改类里的成员的时候,在构造函数的初始化列表中加入新的变量,因为你放弃了使用编译器提供的 default ctor ,那么一旦你自己写的 ctor 出了什么问题,编译器很可能会不会给你提醒的,这就是编译器的“报复”,同样也适用于 operator= 。

  • 如果你正在写继承关系,那么也请你在 derived class 的 ctor 中加上 base class 的 ctor ,同样的,在 operator= 中也需要加入 base class 函数,就像下列这样

class Base {
public:
	...
    Base(const Base& rhs) : p(rhs.name) {};
    Base& operator= (const Base& rhs) {
        name = rhs.name;
        return *this;
    };
    ...
private:
    int name;
};

class Derived : public Base {
public:
    ...
    Derived(const Derived& rhs) : Base(rhs), p(rhs.p) {};	// note!
    Derived& operator= (const Derived&) {
        Base::operator=(rhs);	// note!
        p = rhs.p;
        return *this;
    };
   	...
private:
    int p;
};

参考书籍与文章:

  • 《Effective C++》
  • 《拓跋阿秀校招笔记》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值