《Effective C++》——让自己习惯C++(item 1 ~ item 4)

51 阅读3分钟

Item 1:视 C++ 为一个语言联邦

C++ 中的 4 个次语言:

① c

② Object-Oriented C++

③ Template C++

④ STL

Item 2:尽量以 constenuminline 替换 #define

  • enum hack: 一个属于枚举类型的数值可权充 ints 使用
class GamePlayer
{
private:
    enum { NumTurns = 5 };
    
    int scores[NumTurns];
};
  • “对于单纯常量,最好以 const 对象或 enums 替换 #define
  • “对于形似函数的宏,最好改用 inline 函数替换 #define

Item 3:尽可能使用 const

  • “当 constnon-const 成员函数有着实质等价的实现时,令 non-const 版本调用 const 版本可避免代码重复。”

    例如下面的代码,可以运用 const 成员函数实现出其 non-const 孪生兄弟。

    class TextBlock{
    public:
        ...
        const char& operator[](std::size_t position) const
        {
            ...
            ...
            ...
            return text[position];
        }
        char& operator[](std::size_t position)
        {
            // 省略的代码同上
            ...
            ...
            ...
            return text[position];
        }
    private:
        std::string text;
    }
    

    改为如下代码:

    class TextBlock{
    public:
        ...
        const char& operator[](std::size_t position) const
        {
            ...
            ...
            ...
            return text[position];
        }
        char& operator[](std::size_t position)
        {
            // 为 *this 加上 const,调用 const op[]
            return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
        }
    private:
        std::string text;
    }
    

    上面的代码有两次转型,第一次用来为 *this 添加 const(这使接下来调用 operator[] 时得以调用 const 版本),第二次则是从 const operator[] 的返回值中移除 const

    • static_cast<const TextBlock&>(*this) :将当前对象 *this 转换为 const TextBlock 的引用,确保后续操作可以访问 TextBlock 类的内容,但不允许修改。

    • (*this)[position] :使用下标运算符访问 TextBlock 类的一个元素,通常这应该返回一个 const char 类型的引用,表示该位置的字符。

    • const_cast<char&>(...) :使用 const_cast 去掉返回的 const 限定符,使得你可以得到一个可修改的 char& 引用。这样就可以修改该位置的字符。

Item 4:确定对象使用前已先被初始化

  • 为内置类型对象进行手工初始化,因为 C++ 不保证初始化它们。”

  • “构造函数最好使用成员初值列表,而不要在构造函数本体内使用赋值操作。初值列表列出的成员变量,其排列次序应该和它们在 class 中的声明次序相同。”

    • C++ 有十分固定的 “成员初始化次序”。

      • base classes 更早于其 derived classes 被初始化
      • class 的成员变量总是以其声明次序被初始化
    • C++ 规定,对象的成员变量的初始化动作发生在进入构造函数本体之前,在函数体内的动作是赋值

      • 如果不使用初值列表,会首先调用默认构造函数为成员变量设初值,然后再调用拷贝赋值操作符对它们赋予新值。相比之下,单只调用一次拷贝构造函数是比较高效的,有时甚至高效的多。
  • “为免除“跨编译单元的初始化次序”问题,请以 local static 对象替换 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();    // 使用 tfs 对象
        ...
    }
    
    Directory tempDir(params);
    

    除非 tfs 在 tempDir 之前被初始化,否则 tempDir 的构造函数会用到尚未初始化的 tfs。但是C++ 对“定义于不同的编译单元内的 non-local static 对象”的初始化相对次序并无明确定义。

    解决办法:

    将每个 non-local static 对象搬到自己的专属函数内(该对象在此函数内被声明为 static)。这些函数返回一个 reference 指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。这个方法的基础在于:函数内的 local static 对象会在 “该函数被调用期间” “首次遇上该对象的定义式” 时被初始化:

    // 两个类同前
    ...
    
    FileSystem& tfs()
    {
        static FileSystem fs;
        return fs;
    }
    
    Directory::Directory(params) 
    { 
        ... 
        std::size_t disks = tfs().numDisks(); // 使用 tfs 对象 ,改为 tfs()
        ... 
    }
    
    // 这个函数用来替换 tempDir 对象
    Directory& tempDir()
    {
        static Directory td;
        return td;
    }