<> 读书笔记之第二章 构造/析构/赋值运算

条款5: c++默默编写并调用那些函数

  1. 对于一个空类, 编译器默认会生成default构造函数,析构函数,copy构造函数,copy assignment操作符,
    并且这些都是public且inline的;
  2. 只有当这些函数被需要(即代码中有调用)时才会生成;

    1
    2
    3
    4
    5
    6
    7
    class Empty{
    public:
    Empty() {} // 如果类里面已经自定义了一个构造函数, 则编译器不会生成此构造函数
    Empty(const Empty& rhs) {...} // 单纯的将每一个non-static变量拷贝到目标对象
    ~Empty() {} // 析构函数, 编译器默认的创建的是non-virtual,除非有子类, 并且子类的析构函数是virtual的
    Empty& operator=(const Empty& rhs) {...} // 同copy构造函数
    };
  3. 如果一个类内含reference变量 或 内含const变量,编译器默认生成的copy构造函数和copy assignment操作符
    将不知所措, 所以此时应该自己实现;

  4. 如果base class将copy assignment操作符声明为private, 编译器将拒绝为derived class 生成一个copy
    assignment 操作符, 由于子类无权调用子类的函数, 编译器无能为力, 所以就….

条款6: 若不想使用编译器自动生成的版本,就明确拒绝

  1. 将copy 构造函数 和 copy assignment操作符声明为private且不予实现;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class HomeForSale{
    public:
    ...
    private:
    HomeForSale(const HomeForSale&);
    HomeForSale& operator=(const HomeForSale&);
    };
    // 定义上述代码后, 当客户企图拷贝时, 编译器会阻挠;
    // 但是你不慎在member函数或friend函数之内调,链接器可能会抱怨.
  2. 对于想杜绝在member函数或friend函数内调用的情况,可以声明如下类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Uncopyable {
    protected:
    Uncopyable() {}
    ~Uncopyable() {}
    private:
    Uncopyable(const Uncopyable&);
    Uncopyable& operator=(const Uncopyable&);
    };
    // 然后在需要阻止的时候继承此类就可以了
    class HomeForSale: private Uncopyable {
    ...
    };
    // 由于在编译器生成版的函数中会尝试调用子类的接口, 但是他们是private
    // 的, 所以就会被编译器拒绝而导致编译错误.

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

  1. c++明确指出,当derived class 对象经由一个base class指针被删除, 而该base
    class带有一个non-virtual析构函数时, 其结果是未定义的, 其通常结果是对象
    的derived成分没有被销毁;
  2. 如果一个class不企图被当做一个bas class, 令其析构函数为virtual往往是一个
    馊主意; 由于声明虚函数需要占用额外空间(vptr);
  3. 只有当class内含至少一个virtual函数,才为它声明virtual析构函数;
  4. 标准的string不含任何virtual函数;
  5. 在你需要一个pure class, 但是你又没有pure virtual 函数, 你可以声明一个pure virtual析构函数,
    并且提供定义, 因为编译器会在AWOW的子类的析构函数中创建一个对~AWOW的调用动作, 如果不定义,
    链接器会报错;
    1
    2
    3
    4
    5
    class AWOW{
    public:
    virtual ~AWOW()=0;
    };
    AWOW::~AWOW(){}

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

  1. c++标准不禁止析构函数抛出异常, 但是不鼓励;
  2. 析构函数应该捕捉任何异常, 然后处理掉或结束程序;
  3. 如果客户需要对某个操作函数运行期间抛出的异常作反应, 应该用一个普通函数, 而不是在析构函数中.

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

如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Transaction{
public:
Transaction() { logTransaction(); }
virtual void logTransaction() const=0; // 做出一份因类型不同而不同的日志记录
};
class BugTransaction: public Transaction{
public:
virtual void logTransaction() const;
};
class SellTransaction: public Transaction {
public:
virtual void logTransaction() const;
};

  1. 当derived class构造函数被调用时, base class一定会更早被调用; 而 derived
    class构造函数调用virtual函数时, 调用的是base class版本; 在base class
    构造期间virtual函数绝不会下降到derived class阶层;
  2. 当 derived class 析构函数开始执行, 对象内的 derived class 成员变量便呈现未定义值,
    所以c++视他们仿佛不存在. 进入 base class 析构函数对象就成为一个 base class 对象;
  3. 解决的办法: 将 logTransaction 改为 non-virtual, 然后要求derived class 构造函数传递必要信息给
    Transaction 构造函数, 如下:
    1
    2
    3
    4
    5
    class Transaction {
    public:
    explicit Transaction(const std::string& logInfo);
    void logTransaction(const std::string& logInfo) const;
    }

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

1
2
3
4
int x, y, z;
x = y = z = 15; // 赋值连锁形式
// 由于赋值采用右结合律, 上面被解析为:
x = (y = (z = 15));

为了实现”连锁赋值”, 赋值操作符必须返回一个 reference 直线操作符的左侧实参, 如下:

1
2
3
4
Widget& operator=(const Widget& rhs) {
...
return (*this);
}

同样适用于+=, -=, *=等等.

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

  1. 最初版的opeator=:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Bitmap {
...
};
class Widget{
...
private:
Bitmap* pb;
};
Widget& Widget::operator=(const Widget& rhs) {
delete pb;
pb = new Bitmap(*rhs.pb);
return (*this);
}
// 缺乏自我赋值安全性
  1. 如果rhs 和 this 是同一个对象的话, delete 则会产生错误; 针对这种情况应该添加证同测试:
1
2
3
4
5
6
7
8
Widget& Widget::opeator=(const Widget& rhs) {
if (this == &rhs) return *this;

delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
// 缺乏异常安全性
  1. 上述虽然实现了自我赋值安全性, 但是缺乏异常安全性, 如果delete pb后, new失败后, 岂不是this
    的pb指向了一个无效指针, 我们应该在赋值pb所指东西之前别删除pb:

    1
    2
    3
    4
    5
    6
    7
    Widget& Widget::opeator=(const Widget& rhs) {
    // if (this == &rhs) return *this; // 不是绝对必要, 但是关心效率的话可以加上
    Bitmap* temp = pb;
    pb = new Bitmap(*rhs.pb);
    delete temp;
    return *this;
    }
  2. 替代方案: copy and swap 技术:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Widget{
    void swap(Widget& rhs); // 交换数据
    };

    Widget& Widget::opeator=(const Widget& rhs) {
    Widget temp(rhs);
    swap(temp);
    return (*this);
    }

note:1. 确保当对象自我赋值时, operator =仍然行为良好; 2. 确定任何函数操作多个对象时,
并且其中多个对象是同一个对象时, 其行为仍然正确.

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

设计良好的面向对象系统(OO-System)会将对象的内部封装起来,只留俩个函数负责对象拷贝(复制),
那便是copy构造函数和copy assignment操作符, 称之为copying函数.
note:

  1. copying 函数应该确保复制”对象内的所有成员变量” 及 “所有base class 成分”
  2. 不要尝试以某个copying 函数实现另一个copying 函数. 应该将共同机能放进第三个函数中,
    并由两个coping 函数共同调用.