导读

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Widget {
public:
	Widget();								// default 构造函数
	Widget(const Widget& rhs);				// copy 构造函数
	Widget& operator=(const Widget& rhs);	// copy assignment 操作符
};

Widget w1;			// 调用默认构造函数
Widget w2(w1);		// 调用拷贝构造函数
w1 = w1;			// 调用拷贝赋值操作符
// = 也可以用来调用 copy 构造函数
Widget w3 = w2;		// 调用拷贝构造函数

copy 构造copy赋值的区别:

  • 如果一个新对象被定义(如w3),一定会有个构造函数被调用,不可能调用赋值操作。
  • 如果没有新对象被定义(如前述 w1 = w2),就不会有构造函数被调用,那么就是赋值操作被调用。

01.视C++为一个语言联邦

今天的C++是个 多重范型编程语言,同时 支持过程形式、面向对象形式、函数形式、泛型形式、元编程形式的语言。

将C++视为一个由相关语言组成的联邦; 四种主要的次语言:

  1. C;C++是以C为基础的。区块,语句,预处理器,内置数据类型,数组,指针都来自于C。当以C++内的C成分工作时,高效编程守则映照出C语言的局限性:没有模板,没有异常,没有重载……
  2. Object-Oriented C++;C with Classes所诉求的部分:classes(包括构造函数和析构函数)、封装(encapsulation)、继承(inheritance)、多态(polymorphism)、virtual函数(动态绑定)等等
  3. Template C++;C++的泛型编程部分。由于templates带来的崭新的编程范型 即 template metaprogramming(TMP,模板元编程)
  4. STL。

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

02.尽量以const,enum,inline替换#define

1
2
3
4
5
6
class GamePlayer {
private:
    // static const int Numturns = 5;
    enum {NumTurns = 5}; 		// 当编译器(错误地)不允许”static整数型class常量“完成”in class初值设定“,采用"the enum hack" 令 NumTurns成为5的一个记号名称
    int scores[NumTurns];		// 
};

enum hack的行为某方面说比较像 #define 而不像const。

例如:取一个const的地址是合法的,但取一个enum的地址就不合法,而取一个#define的地址通常也不合法。

”enum hack“是template metaprogramming的基础技术(条款48)

  • 对于单纯常量,最好以const对象或enums替换#defines.
  • 对于形似函数的宏(macros),最好改用inline函数替换#define。

03.尽可能使用const

const

  • 如果const 出现在 * 左边,表示被指物是常量,此const为 底层const;
  • 如果const 出现在 * 右边,表示指针自身是常量,此const为 顶层const。
  • 如果出现在 * 两边,表示被指物和指针两者都是常量。

const成员函数

将const实施于成员函数的目的,是为了确认该成员函数可作用于const对象身上。

  • 使得class接口比较容易被理解。得知哪个函数可以改动对象内容而 哪个对象不行,这很重要。
  • 使”操作const对象“成为可能,这对编写高效代码很关键

两个成员函数如果只是 常量性(constness)不同,可以被重载,如下代码所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class TextBlock {
public:
	...
    const char& operator[](std::size_t position) const 	// operator[] for const 对象
    { return text[position]; }
    char& operator[](std::size_t position)				// operator[] for non-const对象 
    { return text[position]; }
private:
    std::string text;
};
// 可以被这样调用
TextBlock tb("hello");
std::cout << tb[0];	//调用 non-const TextBlock::operator[]
const TextBlock ctb("world");
std::cout << ctb[0];	// 调用const TextBlock::operator[]

const 成员函数不可以更改对象内任何non-static成员变量。

在const 和 non-const成员函数中避免重复 (在non-const成员函数中调用const成员函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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&>(						// 将op[]返回值的const移除
    	  static_cast<const TextBlock&>(*this)	// 为*this加上const
   			[position]							// 调用 const op[]
        );
    }
    // 两次转型:第一次是为*this 添加const,第二次是从const operator[]的返回值中移除const
};
  • 声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体;
  • 当const 和 non-const成员函数有实质等价的实现时,令non-const版本调用const版本可避免代码重复。

04.确定对象被使用前已经先被初始化

使用成员初始化列表的效率 比 基于赋值的版本的构造函数效率更高。因为后者需要先调用 默认构造函数为成员变量设初值然后对它们赋予新值。

成员初始化列表的初始化顺序是按照 成员变量声明的顺序 进行初始化的,而不是初始化列表给定的顺序。

C++有着固定的 ”成员初始化次序“。 base classes更早于其derived classes被初始化,而class的成员变量总是 以其声明次序被初始化。

多个编译单元内的non-local static对象经由”模板隐式具现化(implicit template instantiations)“形成(而后者自己可能也经由”模板隐式具现化“形成),不但不可能决定正确的初始化次序,甚至往往不值得寻找”可决定正确次序“的特殊情况。

解决方法:将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static)。这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。(即 non-local 对象被local static对象替换了,这是Singleton模式的一个常见实现手法)

05.了解C++默默编写并调用哪些函数

空类,如果自己没声明函数,编译器就会为它声明(编译器版本的)一个拷贝构造函数,一个拷贝赋值操作符和一个析构函数;如果没声明任何构造函数,编译器会声明一个默认构造函数。所有这些函数是public且inline的。

编译器产出的析构函数是 non-virtual,除非这个class的base class自身声明有virtual 析构函数。

07.为多态基类声明virtual析构函数

给base class一个virtual析构函数(此规则只适用于带有多态性之的base class上),此后删除derived class对象就会销毁整个对象,包括所有derived class成分。(否则可能出现只消毁了base class成分,而没有销毁derived class成分 这样的一个”局部销毁“现象。)

如果class不含virtual函数,通常表示它并不意图被用作一个base class。任何class只要带有virtual函数都几乎确定应该有一个virtual析构函数。

  • 带多态性质的base classes应该声明一个virtual析构函数,如果class带有任何virtual函数,它就应该拥有一个virtual析构函数;
  • Classes的设计目的如果不是作为base classes使用,或不是为了具备多态性,就不该声明为virtual函数。如 标准string和STL容器都不被设计作为base classes使用。