首页 > 其他分享 >Chapter 2

Chapter 2

时间:2022-08-24 06:16:08浏览次数:45  
标签:Chapter 函数 virtual 析构 copy class 构造函数

2 构造/析构/赋值运算

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

  • 当没有在类中显示声明,编译器则会声明默认版本的 copy 构造函数、 copy assignment 操作符和一个析构函数。

  • 编译器默认生成的函数是 public 和 inline 的。

    class Empty{ };
    Empty e1;//default constructor
    Empty e2(e1);//copy constructor
    e2 = e1;//copy assignment operator
    //初始化都是调用 构造函数 而不是 copy assignment
    Empty e3 = e1;//copy construct
    
  • default contructor调用base classnon-static 成员变量的构造函数和析构函数。

  • base class的析构函数没有声明为virtual,则编译器默认生成的版本是个non-virtual

  • copy constructorcopy assignment operator行为基本一致,都会调用成员变量的对应copy constructorcopy assignment operator(若有),对于内置类型则拷贝其中每一个比特来完成初始化。

  • 有几种情况,编译器拒绝生成默认的copy assignment operator

    1. 类的成员变量含有引用类型。
    2. 类的成员变量含有const类型。
    3. 某个base classcopy assignment operator声明在private中,则作为派生类无法生成默认的函数,因为派生类会处理base class部分。

*请记住 : *

1.编译器可以暗自为 class 创建 default 构造函数、copy构造函数、copy assignment操作符、析构函数。

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

  1. 若手动声明实现相应的函数,则编译器不会自动生成默认的版本,但有时候需要没有参数或者有缺省参数的默认版本。

  2. 若明确不希望所创造的 class 能够赋值,则需要拒绝自动生成的copy constructor

    有几种拒绝方式 :

  • C++11 : 在声明没有参数或有缺省参数的构造函数版本后加上=default ,手动生成默认版本。

    ​ 在需要拒绝生成的函数声明后加上=delete

    例:

    class Empty
    {
      public:
        Empty(size = 0) = default;//手动声明默认版本
        Empty(const Empty &) = delete;//拒绝编译器自动生成的 copy constructor
        Empty& operator=(const Empty &) = delete;
      private:
        int size;
    };
    
  • copy constructorcopy assignment operator声明在private中。此时友元函数和公有成员仍可访问所以不太安全,但如果不去定义它们,连接器仍然会报错。

  • 声明一个阻止赋值的base class

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

请记住 :

1. 为驳回编译器自动提供的功能,可将相应的成员函数声明为private并且不予实现。或使用无法复制的base class。同样可以使用C++11的做法。

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

​ 根据C++的多态特性,基类指针可以指向派生类。例 :

class TimeKeeper{
public:
    TimeKeeper();
    ~TimeKeeper();
};

class AtomicClock : public TimeKeeper {...};
class WaterClock : public TimeKeeper {...};
class WristWatch : public TImeKeeper {...};

//getTimeKeeper()返回派生类指针
TimeKeeper *ptk = getTimeKeeper();//ptk指向派生类,这就是多态特性
delete ptk;

但问题是ptk是一个基类指针,所以当 delete ptk 时,会调用基类的析构函数,而基类的析构函数时non-virtual的,因此 ptk 所指向的派生类中有一部分不从基类继承过来的成员没有被释放,这回造成资源泄露。

解决方法:

​ 为基类析构函数加上virtual

class TimeKeeper{
public:
    TimeKeeper();
    virtual ~TimeKeeper();
};

此时,系统会根据 ptk 实际指向的派生类调用相应的重写的析构函数。

注 : 这是C++通过虚函数表(virtual table)来实现的。
1. 维护一个 vptr指针。
2. vptr指向数组vtbl(virtual table)。
3. 每个带有virtual函数的类都有一个相应的虚函数表vtbl,当对象调用虚函数是,实际 调用的函数取决于vptr所指向的虚函数表,编译器会在其中寻找相应的函数指针。

当你声明抽象基类而又没有相应的纯虚函数时,可以将析构函数声明为纯虚函数,但必须要为它提供定义

*请记住 : *

1. polymorphic(带多态性质的) base classes 应该声明一个 virtual析构函数。如果 class 带有任何 virtual函数(证明要充当某些类的基类),他就应该拥有一个 virtual析构函数。

2. Classes 的设计目的如果不是作为 base classes 使用,或不是为了其多态特性,就不应该声明virtual析构函数。

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

​ 当一个对象销毁时会自动调用析构函数,它负责释放此对象所占用的资源,若在析构函数中出现异常,且在析构函数内没有捕捉,那么该异常会向上传递,或许你在上层捕捉它并做出相应处理或许程序退出,但不要忘了此时你的析构函数还没有执行完,也就是说你的资源没有释放。

​ **解决方法 : **在析构函数中捕捉异常,确保析构函数能够顺利执行完。

*请记住 : *

1. 析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后痛下它们或结束程序。

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

​ 构造函数 : 当在基类构造函数中包含virtual函数,假设该类有派生类,由于在派生类构造函数调用时,会 先调用基类构造函数,此时派生类成员未初始化,那么基类中的virtual函数不会显示多态性, 不会调用相应的virtual函数。

​ 析构函数 : 同理,当派生类的的析构函数运行时,派生类的成员可能会被释放,所以C++干脆把他们视为不存 在,进入base class析构函数后,对象就变成一个基类对象,此时virtual没有意义。

​ 根本原因 : 派生类的基类构造函数构造析构期间,派生类对象被视为base class而不derive dclass

*请记住 : *

1. 在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(相对当前执行构造函数和析构函数的那一层)。

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

​ 这样的以进行连续赋值操作,就像内置类型 :

int z, y, z;
x = y = z = 1;

​ 此条同样适用其他像 +=、-=等与赋值相关的运算符。

请记住 :

1. 令赋值操作符返回一个reference to *this

条款11 在 operator= 中处理 “自我复制”

​ 在定义 operator=函数时,注意要先判同,防止自我赋值,自我复制有时会导致自己的数据被释放掉导致出错。

​ 也可以适用 copy and swap技术,起始就是先将参数复制一份副本,再适用标准库的swap()函数。

请记住 :

1. 确保当对象自我赋值时 operator= 有良好的行为。其中技术包括比较"来源对象"和"目标对象"的地址

、精心周到的语句顺序、以及 copy and swap。

2. 确定任何函数如果操作一个以上的对象,而其中多个对象是同一对象时,其行为仍然正确。

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

​ 当编写一个copying函数时。请确保 :

​ 1. 复制所有 local 成员。

​ 2. 调用所有base class内的适当的 copying函数。

​ 注意,复制构造函数和复制操作符往往有有着相同的操作,但不应该为了去除冗余代码而相互调用

​ 理由:

​ 1. 用 copy assignment操作符调用 copy构造函数 : 试图构造一个已经存在的对象,逻辑上不太合理,可 能代码能实现相应的功能,但不推荐使用。

​ 2. 用 copy构造函数调用copy assignment操作符 : 同上分析。

​ 若想去除冗余代码,可以将冗余代码放进第三个函数中,供两者调用。

请记住 :

1. Copying 函数应该确保赋值“对象内的所有成员变量”及“所有base class成分。”

2. 不要尝试以某 copying函数实现另一个 copying 函数。应该将共同技能放进第三个函数中,共同调用。

标签:Chapter,函数,virtual,析构,copy,class,构造函数
From: https://www.cnblogs.com/Lingh/p/16618435.html

相关文章

  • Chapter 1
    1让自己习惯C++条款01视C++为一个语言联邦C:C++以C为基础,block、语句、预处理器、内置数据类型、数组、指针都来自于C。当使用C++中的C成分工作时,没有模板(Temp......
  • Chapter 3
    资源管理条款13以对象管理资源​ “以对象管理资源“”也被称为“资源取得时机便是初始化时机(RAII)”。获得资源后立即放进管理对象内,即在构造函数中获取资源。管......
  • # Chapter3. 仲裁器专题
    Chapter3.仲裁器专题本专题内容总结自李虹江老师的IC加油站公众号,李老师的讲的内容十分精彩,除了仲裁器还包括异步FIFO、跨时钟域处理,讲的十分透彻,受益匪浅。FixedPri......
  • Chapter8. 单bit信号跨时钟域同步CDC
    Chapter8.单bit信号跨时钟域同步CDC本章导图单bit信号慢到快传输对于电平信号快到慢由于电平信号高电平时间足够长,因此直接打两拍避免亚稳态是可以的。对于边沿信号......
  • Chapter 10 - Archiving (C#实现,实现NSDocument类型窗口的保存和载入)
    这个例子是在Chapter09-NSUndoManager 上继续实现的。所以大家要看前面的例子。xcode布局改变,记住给MainMenu.xib下的Open菜单项添加openDocument:操作关于PersonMo......
  • Chapter 08 - RaiseMan ( C# 实现 + NSDocument类)
    此例子实现了不用ArrayController,基于view-basedtableview实现添加和删除。当然,也可以用ArrayController实现,这样可以省去NSTableViewDelegate和NSTableViewDataSource......
  • Chapter 08 - RaiseMan (C# 实现 + Cell-based tableview绑定)
    这个代码主要展示如何绑定ArrayController到Cell-basedTableview。在发代码前,需要注意几点。1.Person为自定义的class,但是一定要Register为PersonModel。格式{自定义c......