一. 让自己习惯C++

该书的忠告分为两类:一般性的设计策略,以及带有具体细节的特定语言特性。

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

C++是一个多重范型编程语言,同时支持过程形式、面向对象形式、函数形式、泛型形式/元编程形式的语言。主要由以下方面组成:
1)C.
2)Obeject-Oriented C++.
3)Template C++.
4)STL.
应该根据实际的应用目的选择不同的编程形式。

条款02:尽量以const,enum,inline,替换#define

宁可以编译器替换预处理器,因为#define不被视为语言的一部分。
1)声明class的专属常量

class GamePlayer{
private:
      static const int NumTurns = 5; //常量声明式
      in score[NumTurns]; //使用常量
}

当常量为小数时,'in-class'初值设定以及旧式编译器可能并不支持上述语法声明,此时可以将class的专属常量放在定义式中:

class GamePlayer{
private:
      static const int NumTurns; // 声明常量,位于类的头文件
      in score[NumTurns];    //使用常量
}
GamePlayer::NumTurns = 1.35; //位于类的实现文件中,常量定义

若不能通过'in-class'初值设定,需要利用枚举完成:

class GamePlayer{
private:
      enum { NumTurns = 5 }; // ‘enum hack’另NumTurns成为5的一个记号
      int score[NumTurns]; //这是ok的
}

enum的行为与#define更相似,因为可以取出const常量的地址,但是取出enum和define的地址却是不合法的,enum和define还不会导致额外不必要的内存空间分配。事实上,'enum hack' 是模板元编程的基础技术。
请记住:
1)对于单纯常量,请使用const对象或者enums替换#define。
2)对于形似函数的宏,最好改用inline函数替换#define。

条款03:尽可能使用const

const可以在一个函数声明式内,和函数返回值、各参数、函数自身产生关联。
const用于成员函数,可以确认成员函数可作用于const对象,比如重载。
使用const时应注意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) //只调用const op[]
   {
      return const_cast<char&> ( //const_cast移除const限定,static_cast添加const限定;
            static_cast<const TextBlock&>(*this)[position] //将op[]返回值的const移除,再为*this加上const,最后调用const op[];
            );
   }
...
}

条款04:确定对象被参数化前已被初始化

使用对象前,请先确定对象已经被初始化。对于内置类型以外的东西,请使用构造函数完成确保每一个对象的成员都被初始化。但是请不要混淆赋值(assignment)和初始化(initialization)。
考虑一个表现通讯录的class,其构造函数如下:

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;
};
ABEntry::ABEntry(const std::string& name, const std::string& address,const std::list<PhoneNumber>& Phones)
{
      theName = name;
      theAddress = address;
      thePhone = phones;
      numTimeConsulted = 0;  //这些都是赋值,而非初始化。
}

C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前,初始化发生的时间早于赋值。但对于内置类型来说,前述并不成立。因此,构造函数的较佳写法是使用成员初值列方式来替换赋值动作:

ABEntry::ABEntry(const std::string& name, const std::string& address, const std::list<PhoneNumber>& Phones)
         :theName(name),theAddress(address),thePhone(Phones),numTimeConsulted(0) //这些都是初始化
{} //现在构造函数不必有任何动作

本模板比之前的具有更高效率,基于赋值的第一版会首先调用default函数为ABEntry成员设初值,之后再进行赋值动作。第二版的成员初值列则避免了这个问题,因为针对成员初值列中各成员所设置的实参,会被拿去作为各成员变量之构造函数的实参。本例中前三个string对象都是作为copy构造。实际上,对大多数类型而言,比起调用default构造函数再调用copy assignment操作符,只调用copy构造符是很高效的。
对于成员初值列来说,C++有着十分固定的“成员初始化次序”:

class的成员变量总是以其被声明的次序被初始化。
虽然class的成员变量总是以其被声明的次序被初始化,但是对于不同编译单元的non-local static对象的初始化,C++并没有给出明确次序。这是有原因的:
决定他们的初始化次序相当困难,非常困难,根本无解。最常见的情况是:也就是多个编译单元内的non-local static对象经由“模板隐士具现化,implicit template instantiations”。这种情况不可能决定正确的初始化次序。

不同编译单元内定义之non-local static对象的初始化顺序如何解决?
所谓static对象,其寿命从被构造出来直到程序结束为止。函数内的static对象称为local static对象,其他对象称为non-local static对象。
所谓编译单元是指产出单一目标文件的源码,基本上单一源码文件加上头文件。
本问题是:如果某编译单元内的某个non-local static对象的初始化动作使用了另一个编译单元内的non-local static 对象,它所用的这个对象的可能尚未被初始化,这就会导致未定义行为。

请记住:
1)为内置对象进行手工初始化;
2)构造函数最好使用成员初值列初始化代替赋值操作。
3)为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象。

posted @ 2020-11-10 23:46  Viecgg  阅读(62)  评论(0)    收藏  举报