Effective C++ 中文第三版阅读笔记

开一个新坑, 这个坑比较短(???), 应该寒假之前就能解决, 算作是2020年的开年作了.
更新, 寒假前没有完成, 寒假开头赶工.
实际上又因为拖延症拖延到疫情快结束了. 这篇的原时间是 2019 年 12 月 17 日, 现在修改到今天的日期. 还算是敲门砖, 毕竟是今年开的第一个文章.

侧面的目录不方便, 这里再来生成一个:

1. 让自己习惯C++ - Accustoming Yourself to C++

条款 01: 视C++为一个语言联邦 - View C++ as a federation of languages.

条款 02: 尽量以const, enum, inline替换#define - Prefer consts, enums, and inlines to #defines.

#define ASPECT_RATIO 1.653
const char* const authorName = "Scott Meyers";
const std::string authorName("Scott Meyers");
class GamePlayer {
private:
    static const int NumTurns = 5;   // 常量声明式
    int scores[NumTurns];
};

const int GamePlayer::NumTurns;      // 常量定义式
class GamePlayer {
private:
    enum { NumTurns = 5 };     // 被称作 enum hack
                               // 令NumTurns成为5的一个记号名称
    int scores[NumTurns];
};
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
int a = 5, b = 0;
CALL_WITH_MAX(++a, b);
CALL_WITH_MAX(++a, b + 10);

template<typename T>
inline void callWithMax(const T& a, const T& b) {
    f(a > b ? a : b);
}

总结:

条款 03: 尽可能使用const - Use const whenever possible.

char greeting[] = "hello";
char* p = greeting;              // non-const pointer, non-const data
const char* p = greeting;        // non-const pointer, const data
char* const p = greeting;        // const pointer, non-const data
const char* const p = greeting;  // const pointer, const data
std::vector<int> vec;
/*...*/
const std::vector<int>::iterator iter =    // 类似 T* const
    vec.begin();
*iter = 10;                                // 没问题
++iter;                                    // 错误! iter是const

std::vector<int>::const_iterator cIter =   // cIter的作用像个const T*
    vec.cbegin();
*cIter = 10;                               // 错误! *cIter是const
++cIter;                                   // 没问题
class Rational { /*...*/ };
const Rational operator* (const Rational& lhs, const Rational& rhs);
class TextBlock {
public:
    /*...*/
    const char& operator[](std::size_t position) const
    { return text[position]; }      // operator[] for const对象
    char& operator[](std::size_t position)
    { return text[position]; }      // operator[] for non-const对象
private:
    std::string text;
};

TextBlock tb("Hello");
std::cout << tb[0];                 // call non-const TextBlock::operator[]
const TextBlock ctb("Hello");
std::cout << ctb[0];                // call const TextBlock::operator[]

tb[0] = 'x';                        // No problem
ctb[0] = 'x';                       // ERROR!
class CTextBlock {
public:
    /*...*/
    std::size_t length() const;
private:
    char* pText;
    mutable std::size_t textLength;
    mutable bool lengthIsValid;
};
std::size_t CTextBlock::length() const
{
    if (!lengthIsValid) {
        textLength = std::strlen(pText);
        lengthIsValid = true;
    }
    return textLength;
}
class Textblcok {
public:
    /*...*/
    const char& operator[](std::size_t position) const
    {
        /*...*/     // 边界检测 (bounds checking)
        /*...*/     // 志记数据访问 (log access data)
        /*...*/     // 检验数据完整性 (verify data integrity)
        return text[postion];
    }
    char& operator[](std::size_t postion)
    {
        return 
          const_cast<char&>(
            static_cast<const TextBlock&>(*this)
                [position]
          );
    }
};

总结:

条款 04: 确定对象被使用前已先被初始化 - Make sure that objects are initialized before they’re used.

class PhoneNumber { /*...*/ };
class ABEntry {
public:
    ABEntry(const std::string& name, const std::string& address,
            const std::list<PhoneNumber>& phones);
private:
    std::string theName;
    std::string theAddress;
    std::list<PhoneNumber> thePhones;
    int numTimesConsulted;
};

// Version 1
ABEntry::ABEntry(const std::string& name, const std::string& address,
                 const std::list<PhoneNumber>& phones)
{
    theName = name;           // 这些都是赋值(assignments)
    theAddress = address;
    thePhones = phones;
    numTimesConsulted = 0;
}

// Version 2
ABEntry::ABEntry(const std::string& name, const std::string& address,
                 const std::list<PhoneNumber>& phones)
   :theName(name),            // 这些都是初始化(initializations)
    theAddress(address),
    thePhones(phones),
    numTimesConsulted(0)
{ }

我一直很喜欢用的使用一个private成员函数包含所有成员的赋值操作, 为(wei2)所有构造器调用的方法, 在书中被叫做”伪初始化”(pseudo-initialization), 在书中被拒绝了 – 尽量使用成员初值列完成”真正的初始化”.

最后讨论”不同编译单元内定义的non-local static对象”的初始化次序.

// 库中的
class FileSystem {
public:
    /*...*/
    std::size_t numDisks() const;  // 众多成员函数之一
    /*...*/
};
extern FileSystem tfs;

// 程序库用户建立的类
class Directory {
public:
    Directory( /*params*/ );
    /*...*/
};
Directory::Directory( /*params*/ )
{
    /*...*/
    std::size_t disks = tfs.numDisks();
    /*...*/
}

Directory tempDir( /*params*/ );
{
    static FileSystem fs;
    return fs;
}

class Directory { /*...*/ };
Directory::Directory( /*params*/ )
{
    /*...*/
    std::size_t disks = tfs().numDisks();
    /*...*/
}
Directory& tempDir()
{
    static Directory td;
    return td;
}

总结:

2. 构造/析构/赋值运算 - Constructors, Destructors, and Assignment Operators

条款 05: 了解C++默默编写并调用哪些函数 - Know what functions C++ silently writes and calls.

template<typename T>
class NamedObject {
public:
    NamedObject(const char* name, const T& value);
    NamedObject(const std::string& name, const T& value);
    /*...*/
private:
    std::string nameValue;
    T objectValue;
};

NamedObject<int> no1("Smallest Prime Number", 2);
NamedObject<int> no2(no1);

总结:

条款 06: 若不想使用编译器自动生成的函数, 就该明确拒绝 - Explicitly disallow the use of compiler-generated functions you do not want.

class Uncopyable {
protected:
    Uncopyable() { }                          // 允许派生对象构造和析构
    ~Uncopyable() { }
private:
    Uncopyable(const Uncopyable&);            // 但是阻止拷贝行为
    Uncopyable& operator=(const Uncopyable&);
};

class HomeForSale: private Uncopyable {       // class不再声明
    /*...*/                                       // 拷贝构造和拷贝
};                                            // 赋值运算符

总结:

条款 07: 为多态基类声明virtual析构函数 - Declear destructors virtual in polymorphic base classes.

class TimeKeeper {
public:
    TimeKeeper();
    ~TimeKeeper();
    /*...*/
};
class AtomicClock: public TimeKeeper { /*...*/ };   // 原子钟
class WaterClock: public TimeKeeper ( /*...*/ );    // 水钟
class WristWatch: public TimeKeeper { /*...*/ };    // 腕表

TimeKeeper* getTimeKeeper();        // 返回一个指针, 指向一个
                                    // TimeKeeper派生类的动态分配对象

TimeKeeper* ptk = getTimeKeeper();  // 从TimeKeeper继承体系
                                    // 获得一个动态分配对象
/*...*/                                 // 运用它 /*...*/ 
delete ptk;                         // 释放它, 避免资源泄漏
class TimeKeeper {
public:
    TimeKeeper();
    virtual ~TimeKeeper();
    /*...*/
};
TimeKeeper* ptk = getTimeKeeper();
/*...*/
delete ptk;                             // Now, right!
class AWOV {                 // AWOV = "Abstract w/o Virtuals"
public:
    virutal ~AWOV() = 0;     // 声明纯虚析构函数
};

总结:

条款 08: 别让异常逃离析构函数 - Prevent exceptions from leaving destructors.

class DBConnection {                   // 假设这是一个用于数据库连接的类
public:
    /*...*/
    static DBConnection create();      // /*...*/
    void close();                      // 关闭连接, 失败则抛出异常
};

class DBConn {                         // DBConnection的管理类, 见第三章
public:
    /*...*/
    // Version O
    ~DBConn()                          // 确保数据库连接总是会被关闭
    {
        db.close();
    }
private:
    DBConnection db;
};

{                                       // 开启一个区块
    DBConn dbc(DBConnection::create()); // 建立一个DBConnection对象并交给DBConn对象管理
    /*...*/                                 // 通过DBConn的接口使用DBConnection对象
}                                       // 在区块结束点, DBConn对象被销毁
                                        // 因而自动为DBConnection对象调用close

// Version 1
DBConnn::~DBConn()
{
    try { db.close(); }
    catch (/*...*/) {
        制作运转记录, 记下对close的调用失败;
        std::abort();
    }
}

// Version 2
DBConn::~DBConn()
{
    try { db.close; }
    catch (/*...*/) {
        制作运转记录, 记下对close的调用失败;
    }
}
// Version 3
class DBConn {
public:
    /*...*/
    void close()
    {
        db.close();
        closed = true;
    }
    ~DBConn()
    {
        if (!closed) {
            try {
                db.close();
            }
            catch (/*...*/) {
                制作运转记录, 记下对close的调用失败;
                /*...*/       // 吞下异常
            }
        }
    }
private:
    DBConnection db;
    bool closed;
};

总结:

条款 09: 绝不在构造和析构过程中调用虚函数 - Never call virtual functions during construction or destruction.

class Transaction {
public:
    Transaction();                              // 所有交易的基类
    virtual void logTransaction() const = 0;    // 做出一份因类型不同而不同
                                                // 的日志记录
    /*...*/
};

Transaction::Transaction()                      // 基类的构造函数实现
{
    /*...*/
    logTransaction();                           // 最后动作是记录这笔交易
}

class BuyTransaction: public Transaction {
public:
    virtual void logTransaction() const;        // 记录此类交易
    /*...*/
};

class SellTransaction: public Transaction {
public:
    virtual void logTransaction() const;        // 同上
    /*...*/
};

BuyTransaction b;
class Transaction {
public:
    Transaction()
    { init(); }                   // 调用非虚函数
    virtual void logTransaction() const = 0;
private:
    void init()
    {
        /*...*/
        logTransaction();         // 这里调用虚函数
    }
};
class Transaction {
public:
    explicit Transaction(const std::string& logInfo);
    void logTransaction(const std::string& logInfo) const;
    /*...*/
};
Transaction::Transaction(const std::string& logInfo)
{
    /*...*/
    logTransaction(logInfo);
}
class BuyTransaction: public Transaction {
public:
    BuyTransaction( *parameters* )
     : Transaction(createLogString( *parameters* ))
    { /*...*/ }
    /*...*/
private:
    static std::string createLogString( *parameters* );
};

总结:

条款 10: 令operator=返回一个引用指向*this - Have assignment operators return a reference to *this.

int x, y, z;
x = y = z = 15;        // 赋值连锁形式
x = (y = (z = 15));    // <解析>
class Widget {
public:
    /*...*/
    Widget& operator+=(const Widget& rhs)
    {
        /*...*/
        return *this;
    }
    Widget& operator=(const Widget& rhs)
    {
        /*...*/
        return *this;
    }
    /*...*/
};

总结:

条款 11: 在operator=中处理自我赋值 - Handle assignment to self in operator=.

class Widget { /*...*/ };
Widget w;
/*...*/
w = w;                   // 愚蠢的自我赋值
a[i] = a[j];             // 潜在的自我赋值
*px = *py;               // 潜在的自我赋值

class Base { /*...*/ };
class Derived: public Base { /*...*/ };
void doSomething(const Base& rb, Derived* pd);  // 更加潜在的
                            //rb和*pd可能指向同一个对象
class Bitmap { /*...*/ };
class Widget {
    /*...*/
private:
    Bitmap* pb;
};

// 不安全版本
Widget& Widget::operator=(const Widget& rhs)
{
    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;
}

// 安全版本
Widget& Widget::operator=(const Widget& rhs)
{
    if (this == &rhs) return *this;

    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;
}

// 异常安全性(exception safety)
Widget& Widget::operator=(const Widget& rhs)
{
    Bitmap* pOrig = pb;
    pb = new Bitmap(*rhs.pb);
    delete pOrig;
    return *this;
}

// 证同测试+异常安全性(可能会降低效率)
Widget& Widget::operator=(const Widget& rhs)
{
    if (this == &rhs) return *this;

    Bitmap* pOrig = pb;
    pb = new Bitmap(*rhs.pb);
    delete pOrig;
    return *this;
}
class Bitmap { /*...*/ };
class Widget {
    /*...*/
    void swap(Widget& rhs);   // 交换*this和rhs的数据, 详见条款29
    /*...*/
};

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

// 另一个精巧的版本
Widget& operator=(Widget rhs)    // 副本在值传递的时候产生
{
    swap(rhs);
    return *this;
}

总结:

条款 12: 复制对象时勿忘其每一个成分 - Copy all parts of an object.

void logCall(const std::string& funcName);
class Customer {
public:
    /*...*/
    Customer(const Customer& rhs);
    Customer& operator=(const Customer& rhs);
    /*...*/
private:
    std::string name;
};

Customer::Customer(const Customer& rhs)
  : name(rhs.name)
{
    logCall("Customer copy constructor");
}

Customer& Customer::operator=(const Customer& rhs)
{
    logCall("Customer copy assignment operator");
    name = rhs.name;
    return *this;
}

// Update
class Date { /*...*/ }l
class Customer {
public:
    /*...*/
private:
    std::string name;
    Date lastTransaction;
};
class PriorityCustomer: public Customer {
public:
    /*...*/
    PriorityCustomer(const PriorityCustomer& rhs);
    PriorityCustomer& operator=(const PriorityCustomer& rhs);
    /*...*/
private:
    int priority;
};

PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs) 
  : priority(rhs.priority)
{
    logCall("PriorityCustomer copy constructor");
}

PriorityCustomer&
PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
    logCall("PriorityCustomer copy assignment operator");
    priority = rhs.priority;
    return *this;
}

PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs) { logCall(“PriorityCustomer copy assignment operator”); Customer::operator=(rhs); priority = rhs.priority; return *this; }


> 令拷贝赋值操作符调用拷贝构造函数时不合理的, 因为这就像试图构造一个已经存在的对象. 这件事如此荒谬, 乃至于根本没有相关语法. 是有一些看似如你所愿的语法, 但其实不是; 也的确有些语法背后真正做了它, 但它们在某些情况下会造成你的对象败坏, 所以我不打算将那些语法呈现给你看. 单纯地接受这个叙述吧: 你不该令拷贝赋值操作符调用拷贝构造函数.    
> 反方向 -- 令拷贝构造函数调用拷贝赋值操作符 -- 同样无意义. 构造函数用来初始化新对象, 而赋值操作符只施行于已初始化对象身上. 对一个尚未构造好的对象赋值, 就像在一个尚未初始化的对象身上做"只对已初始化对象才有意义"的事一样. 无聊嘛! 别尝试. 

- 在条款9中提到的, 使用一个`init`函数去替换多构造函数中重复的部分的类似做法, 同样可以运用于这俩拷贝函数上.  

总结:
- 拷贝函数应该缺包复制"对象内的所有成员变量"及"所有基类成分".  
- 不要尝试以某个拷贝函数实现另一个拷贝函数. 应该将共同机能放进第三个函数中, 并由两个拷贝函数共同调用.


## 3. 资源管理 - Resource Management
### 条款 13. 以对象管理资源 - Use objects to manage resources. 
```c++
class Investment { /*...*/ };          // root class
Investment* createInvestment();    // 返回指针, 指向 Investment 体系内的动态分配对象.
                                   // 调用者有责任删除它

// ver.1
void f()
{
    Investment* pInv = createInvestment();
    /*...*/
    delete pInv;                   // **该 delete 坑会因为各种原因无法访达**
}

// ver.2
void f()
{
    std::auto_ptr<Investment> pInv(createInvestment());
    /*...*/
}

通过这个简单的例子示范”以对象管理资源”的两个关键思想:

std::auto_ptr<Investment> pInv1(createInvestment());   // pInv1 指向返回物
std::auto_ptr<Investment> pInv2(pInv1);                // pInv2 指向对象, pInv1 被设置为 null
pInv1 = pInv2;                    // pInv1 指向对象, pInv2 被设置为 null
void f()
{
    /*...*/
    std::tr1::shared_ptr<Investment> pInv1(createInvestment());  // pInv1 指向返回物
    std::tr1::shared_ptr<Investment> pInv2(pInv1);     // pInv1 和 pInv2 指向同一个对象
    pInv1 = pInv2;    // 无变化
}
std::auto_ptr<std::string> aps(new std::string[10]);  // 错误
std::tr1::shared_ptr<int> spi(new int[1024]);         // 错误

总结:

条款 14. 在资源管理类中小心复制行为 - Think carefully about copying behavior in resource-managing classes.

条款 13 中提到 RAII, 只适用于分配在堆上的资源. 对于其他的资源, 智能指针往往不适合作为资源掌管者. 所以, 有可能需要建立自己的资源管理类.

void lock(Mutex* pm);       // 锁定 pm 所指的互斥器
void unlock(Mutex* pm);     // 将互斥器解除锁定

class Lock {
public:
    explicit Lock(Mutex* pm)
     : mutexPtr(pm)
    { lock(mutexPtr); }
    ~Lock() { unlock(mutexPtr); }
private:
    Mutex* mutexPtr;
};

Mutex m;
{                           // 建立一个作用域用来定义 critical section
    Lock m1(&m);            // 构造器会帮助锁定互斥器
}                           // 作用域末尾, 析构器调用, 自动解除互斥器锁定
Lock ml1(&m);               // 锁定 m
Lock ml2(ml1);              // 将 ml1 复制到 ml2 身上. 会发生什么?
class Lock {
public:
    explicit Lock(Mutex* pm)
     : mutexPtr(pm, unlock)      // 以某个 Mutex 初始化 shared_ptr, 第二个参数为删除器
    {
        lock(mutexPtr.get());
    }
private:
    std::tr1::shared_ptr<Mutex> mutexPtr;
};

还有一种拷贝方法是执行深度拷贝(deep copying).

总结: