Effective C++ 笔记(1)

第一部分: 让自己习惯C++

条款01:视C++为一个语言联邦

请记住

-C++高效编程守则视状况而变化,取决于你使用C++的哪一部分。


条款02,03:尽量以const, enum, inline替换#define,尽可能使用const

对于指针变量的const

对于指针变量的const, 根据const和星号的相对位置,有不同的行为
左定值,右定向

const对于迭代器

//iter的作用像个T* const
const std::vector<int>::iterator iter = vec.begin();
//cIter的作用像个const T*
std::vector<int>::const_iterator cIter = vec.begin();

const与函数声明

class Rational (...);
const Rational operator *(const Rational& lhs, const Rational& rhs);

Rational之前的const表示函数返回一个常量值,用以避免这样的暴行

(a*b)=c;//如果没有const,不报错,有const报错

const成员函数

\(bitwise\) \(const\)声明
在成员函数的括号后面加const,告诉编译器我不会修改这个对象的任何成员变量。
然而有些成员函数不具有const性质,却能通过bitwise测试

class CTextBlock {
    public:
    ...
    char &operator [](std::size_t position) const {//bitwise const声明
        return pText[position];
    }
    private:
    char *pText;
}

通过了bitwise const 测试,却允许发生这种事

const CTextBlock cctb("Hello");
char *pc = &cctb[0];
*pc = 'J';

\(logical\) \(const\)
一个const成员函数可以修改对象内的某些bits,只要客户端侦测不出来

mutable前缀修饰的变量可以在const成员函数中被修改

class CTextBlock{
    public:
        ...
        std::size_t length() const;
    private:
        char* pText;
        mutable std::size_t textLength;
        mutable bool lengthIsValid;
};
//logical const
//虽然有修改,但是修改的是mutable变量,所以通过bitwise测试
std::size_t CTexeBlock::length() const {
    if(!lengthIsValid) {
        textLength = std::strlen(pText);
        lengthIsValid = true;
    }
    return textLength;
}

在const和non-const中避免重复

对于 non-const 版本的函数可以采取调用 const 版本的函数的方法来实现。(避免代码重复)

class TextBlock {
   public:
    const char &operator[](int i) const { return text[i]; }
    char &operator[](int i) { return const_cast<char &>(static_cast<const TextBlock &>(*this)[i]); }
    TextBlock(std::string s) : text(s) {}

   private:
    std::string text;
};

这里使用的const_caststatic_cast类型转换。
值得注意的是,反向做法是不应该的。const 成员函数承诺绝不改变其对象的逻辑状态,但是 non-const 成员函数没有承诺,在 const 成员函数内调用 non-const 成员函数是有风险的。

请记住
  • 将某些东西声明为 const 可帮助编译器侦测出错误用法。const 可被施加于任何作用域的对象、函数参数、函数返回类型、成员函数本体。
  • 编译器强制实施 bitwise constness,但编写程序时应该使用“概念上的常量性” (conceptual constness)。
  • 当 const 和 non-const 成员函数有着实质等价的实现时,令 non-const 版本调用 const 版本可避免代码重复。

条款04:确定对象被使用前已被初始化

构造函数

对于内置类型,需要自己手动完成初始化
对于内置类型以外的,初始化责任落在构造函数中
值得注意的是,初始化时使用列表初始化替换赋值操作可以提高效率
因为在大括号内赋值的版本,非内置类型会首先调用default构造函数再赋值(内置类型赋值和初始化代价相同)
而列表初始化则避免了default初始化(如果成员变量在"成员初始列"中未被指定初值,则会自动调用default)

ABEntry::ABEntry()
    :theName(),         //调用theName的default构造函数
    theAddress(),
    thePhones(),
    numTimesConsulted(0)//内置类型,必须显式初始化
    {}

即使成员变量是内置类型,有时也一定要使用初值列,当成员变量是const或者reference,它们一定需要初值,不能被赋值

最简单的方法就是总是使用成员初始列


成员初始化次序

C++有着固定的"成员初始化次序",即使成员在成员初始列中的出现顺序可以有很多种。
在成员初始列中条列各个成员时,为了避免错误,最好总以声明次序为次序。


不同编译单元内"non-local static对象"的初始化次序

问题是:如果某编译单元内的non-local static对象的初始化动作使用了另一编译单元内的某个non-local static对象,它所用到的这个对象可能还未被初始化,因为C++中对于"定义于不同编译单元内的non-local static对象"的初始化次序并无明确定义。
解决方案:将每个non-local static对象搬到自己的专属函数中(该对象在函数内被声明为static),这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。
这个手法的基础在于:C++保证,函数内的local static对象会在"该函数被调用期间""首次遇上该对象之定义式"时被初始化。

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

这样的方式仍在多线程中带有不确定性,任何一种non-const static对象,不论local或是non-local,在多线程环境下"等待某事发生"都会有麻烦。
处理这个麻烦的一种做法是:在程序的单线程启动阶段手动调用所有的reference-retruning函数,这可消除与初始化有关的"竞速形势"。

请记住
  • 为内置型对象进行手工初始化,因为 C++ 不保证初始化它们。
  • 构造函数最好使用成员初值列 (member initialization list),而不要在构造函数本体内使用赋值操作。初值列列出的成员变量,其排列次序应该和他们在 class 中声明次序相同。
  • 为免除“跨编译单元之初始化次序”问题,请以 local static 对象替换 non-local static 对象。
posted @ 2020-11-18 09:21  thhyj  阅读(65)  评论(0编辑  收藏  举报