IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> Effective C++ 阅读笔记之Chapter2 -> 正文阅读

[C++知识库]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++》
  • 《拓跋阿秀校招笔记》
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-10-28 12:13:13  更:2021-10-28 12:14:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 5:37:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码