Open zixie1991 opened 7 years ago
有人说C++程序员可以分为两类,读过Effective C++的和没读过的。世界顶级C++大师Scott Meyers 成名之作的第三版的确当得起这样的评价。
本书并没有你告诉什么是C++语言,怎样使用C++语言,而是从一个经验丰富的C++大师的角度告诉程序员:怎么样快速编写健壮的,高效的,稳定的,易于移植和易于重用的C++程序。
本书共分为9节55个条款,从多个角度介绍了C++的使用经验和应遵循的编程原则。
本条款提示读者,C++已经不是一门很单一的语言,而应该将之视为一个由相关语言组成的联邦。从语言形式上看,它是一个多重范型编程语言(multiparadigm programminglanguage) ,一个同时支持过程形式(procedural)、面向对象形式(object-oriented)、函数形式(functional) 、泛型形式(generic) 、元编程形式(metaprogramming )的语言,从语言种类上看,它由若干次语言组成,分别为:
(1) C。说到底C++ 仍是以C 为基础。区块(blocks) 、语句( statements) 、预处理器( preprocessor) 、内置数据类型(built-in data types) 、数组(arrays) 、指针(pointers) 等统统来自C。
(2) Object-Oriented C++。这部分也就是C with Classes 的: classes (包括构造函数和析构函数) ,封装( encapsulation) 、继承( inheritance) 、多态(polymorphism) 、virtual 函数(动态绑定) ……
(3) Template C++。这是C++ 的泛型编程(generic programming) 部分,也是大多数程序员经验最少的部分。Template 相关考虑与设计己经弥漫整个C++,实际上由于templates 威力强大,它们带来崭新的编程范型(programming paradigm) ,也就是所谓的templatemetaprogramming (TMP,模板元编程)
(4) STL。 STL 是个template 程序库,它是非常特殊的一个。它对容器(containers) 、迭代器(iterators) 、算法(algorithms) 以及函数对象(function objects) 的规约有极佳的紧密配合与协调。
本条款讨论了C语言中的#define在C++程序设计中的带来的问题并给出了替代方案。
C语言中的宏定义#define只是进行简单的替换,对于程序调试,效率来说,会带来麻烦,在C++中,提倡使用const,enum和inline代替#define;然而,有了consts 、enums 和inlines,我们对预处理器(特别是#define) 的需求降低了,但并非完全消除。#include 仍然是必需品,而#ifdef/#ifndef 也继续扮演控制编译的重要角色。目前还不到预处理器全面引迫的时候。
本条款总结了Const的使用场景和使用它带来的好处。
关键字canst 多才多艺。你可以用它在classes 外部修饰global 或namespace作用域中的常量,或修饰文件、函数、或区块作用域(block scope) 中被声明为static 的对象。你也可以用它修饰classes 内部的static 和non-static 成员变量。面对指针,你也可以指出指针自身、指针所指物,或两者都(或都不〉是const。你应该尽可能地使用const,这样降低程序错误,使程序易于理解。
此外,一个编程技巧是:当const 和non-const 成员函数有着实质等价的实现时,令non-const 版本调用const 版本可避免代码重复:
class TextBlock { public: const char& operator[] (std::size_t position) const { …… return text[position]; } char& operator[] (std::size t position) { return const_cast<char&>( static_cast<const TextBlock&>(*this) [position]); //将op[]返回值的const 转除为*this 加上cons, 调用const op[] } };
本条款告诫程序员,在C++程序设计中,应该对所有对象初始化,以避免不必要的错误,同时,给出了高效初始化对象的方法和正确初始化对象的方法。
(1)初始化构造函数最好使用成员初值列(member initialization list) ,而不要在构造函数本体内使用赋值操作(assignment) 。初值列出的成员变量,其排列次序应该和它们在class 中的声明次序相同。
考虑一个用来表现通讯簿的class ,其构造函数如下:
class PhoneNumber { ... }; class ABEntry { //ABEntry =“Address Book Entry" 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& nane , const std: : string& address, const std::list<PhoneNumber>& phones) theName = narne; //这些都是赋值(assignments) , theAddress = address; //不是始化(initializations)。 thePhones = phones; numTimesConsulted = 0; int num TimesConsulted; }
正确而又高效的初始化对象的方法是:
ABEntry: :ABEntry(const std: :string& nane , const std: : string& address, const std::list<PhoneNumber>& phones) : theName(name), theAddress(address), //这些都是初始化 thePhones(phones), numTimesConsulted(0) {} // 构造函数体是空的
C++ 有着十分固定的”成员初始化次序”。次序总是相同: base class早于其derived classes 被初始化,而class 的成员变量总是以其声明次序被初始化。回头看看ABEntry. 其theName 成员永远最先被初始化,然后是theAddress,再来是thePhones,最后是numTimesConsulted。即使它们在成员初值列中以不同的次序出现(很不幸那是合法的),也不会有任何影响。
(2)C++ 对”定义于不同编译单元内的non-local static 对象”的初始化次序并无明确定义。为免除”跨编译单元之初始化次序”问题,请以local static 对象替换non-local static 对象。
class FileSystem { ... }; FileSystem& tfs() //代替tfs对象 { static FileSystem fs; // 以local static的方式定义和初始化object return fs; // 返回一个引用 } class Directory { ... }; Directory::Directory( params ) { ... std::size_t disks = tfs().numDisks(); ... } Directory& tempDir() // 代替tempDir对象, { static Directory td; return td; }
本条款告诉程序员,编译器自动为你做了哪些事情。
用户定义一个empty class (空类),当C++ 处理过它之后,如果你自己没声明,编译器就会为它声明(编译器版本的)一个copy 构造函数、一个copy assignment操作符和一个析构函数。此外如果你没有声明任何构造函数,编译器也会为你声明一个default 构造函数。所有这些函数都是public 且inline 。举例,如果你写下:
class Empty { };
这就好像你写下这样的代码:
class Empty { public: Empty() { ... } Empty(const Empty& rhs) { ... ) -Empty( ) { ... } Empty& operator=(const Empty& rhs) { ... } };
需要注意的是,只要你显式地定义了一个构造函数(不管是有参构造函数还是无参构造函数),编译器将不再为你创建default构造函数。
本条款告诉程序员,如果某些对象是独一无二的(比如房子),你应该禁用copy 构造函数或copy assignment 操作符,可选的方案有两种:
(1) 定义一个公共基类,让所有独一无二的对象继承它,具体如下:
class Uncopyable { protected: //允许derived对象构造和析构 Uncopyable () {} -Uncopyable(} { } private: Uncopyable(const Uncopyable&}; //但阻止copying Uncopyable& operator=(const Uncopyable&); };
为阻止HomeForSale对象被拷贝,唯一需要做的就是继承Uncopyable:
class HomeForSale: private Uncopyable { … };
这种方法带来的问题是,可能造成多重继承,这回导致很多麻烦。
(2) 创建一个宏,并将之放到每一个独一无二对象的private中,该宏为:
// 禁止使用拷贝构造函数和 operator= 赋值操作的宏 // 应该类的 private: 中使用 #define DISALLOW_COPY_AND_ASSIGN(TypeName) \ TypeName(const TypeName&); \ void operator=(const TypeName&)
这种方法比第一种方法好,google C++编程规范中提倡使用该方法。
Effective C++
有人说C++程序员可以分为两类,读过Effective C++的和没读过的。世界顶级C++大师Scott Meyers 成名之作的第三版的确当得起这样的评价。
本书并没有你告诉什么是C++语言,怎样使用C++语言,而是从一个经验丰富的C++大师的角度告诉程序员:怎么样快速编写健壮的,高效的,稳定的,易于移植和易于重用的C++程序。
本书共分为9节55个条款,从多个角度介绍了C++的使用经验和应遵循的编程原则。
1. 让自己习惯C++(Accustoming your self to C++)
条款01: 视C++ 为一个语言联邦
本条款提示读者,C++已经不是一门很单一的语言,而应该将之视为一个由相关语言组成的联邦。从语言形式上看,它是一个多重范型编程语言(multiparadigm programminglanguage) ,一个同时支持过程形式(procedural)、面向对象形式(object-oriented)、函数形式(functional) 、泛型形式(generic) 、元编程形式(metaprogramming )的语言,从语言种类上看,它由若干次语言组成,分别为:
(1) C。说到底C++ 仍是以C 为基础。区块(blocks) 、语句( statements) 、预处理器( preprocessor) 、内置数据类型(built-in data types) 、数组(arrays) 、指针(pointers) 等统统来自C。
(2) Object-Oriented C++。这部分也就是C with Classes 的: classes (包括构造函数和析构函数) ,封装( encapsulation) 、继承( inheritance) 、多态(polymorphism) 、virtual 函数(动态绑定) ……
(3) Template C++。这是C++ 的泛型编程(generic programming) 部分,也是大多数程序员经验最少的部分。Template 相关考虑与设计己经弥漫整个C++,实际上由于templates 威力强大,它们带来崭新的编程范型(programming paradigm) ,也就是所谓的templatemetaprogramming (TMP,模板元编程)
(4) STL。 STL 是个template 程序库,它是非常特殊的一个。它对容器(containers) 、迭代器(iterators) 、算法(algorithms) 以及函数对象(function objects) 的规约有极佳的紧密配合与协调。
条款02: 尽量以const, enum, inline替换#define
本条款讨论了C语言中的#define在C++程序设计中的带来的问题并给出了替代方案。
C语言中的宏定义#define只是进行简单的替换,对于程序调试,效率来说,会带来麻烦,在C++中,提倡使用const,enum和inline代替#define;然而,有了consts 、enums 和inlines,我们对预处理器(特别是#define) 的需求降低了,但并非完全消除。#include 仍然是必需品,而#ifdef/#ifndef 也继续扮演控制编译的重要角色。目前还不到预处理器全面引迫的时候。
条款03: 尽可能使用const
本条款总结了Const的使用场景和使用它带来的好处。
关键字canst 多才多艺。你可以用它在classes 外部修饰global 或namespace作用域中的常量,或修饰文件、函数、或区块作用域(block scope) 中被声明为static 的对象。你也可以用它修饰classes 内部的static 和non-static 成员变量。面对指针,你也可以指出指针自身、指针所指物,或两者都(或都不〉是const。你应该尽可能地使用const,这样降低程序错误,使程序易于理解。
此外,一个编程技巧是:当const 和non-const 成员函数有着实质等价的实现时,令non-const 版本调用const 版本可避免代码重复:
条款04: 确定对象被使用前已先被初始化
本条款告诫程序员,在C++程序设计中,应该对所有对象初始化,以避免不必要的错误,同时,给出了高效初始化对象的方法和正确初始化对象的方法。
(1)初始化构造函数最好使用成员初值列(member initialization list) ,而不要在构造函数本体内使用赋值操作(assignment) 。初值列出的成员变量,其排列次序应该和它们在class 中的声明次序相同。
考虑一个用来表现通讯簿的class ,其构造函数如下:
正确而又高效的初始化对象的方法是:
C++ 有着十分固定的”成员初始化次序”。次序总是相同: base class早于其derived classes 被初始化,而class 的成员变量总是以其声明次序被初始化。回头看看ABEntry. 其theName 成员永远最先被初始化,然后是theAddress,再来是thePhones,最后是numTimesConsulted。即使它们在成员初值列中以不同的次序出现(很不幸那是合法的),也不会有任何影响。
(2)C++ 对”定义于不同编译单元内的non-local static 对象”的初始化次序并无明确定义。为免除”跨编译单元之初始化次序”问题,请以local static 对象替换non-local static 对象。
2. 构造/析构/赋值运算(Constructors,Destructors,and Assignment Operators)
条款05: 了解C++ 默默编写并调用哪些函数
本条款告诉程序员,编译器自动为你做了哪些事情。
用户定义一个empty class (空类),当C++ 处理过它之后,如果你自己没声明,编译器就会为它声明(编译器版本的)一个copy 构造函数、一个copy assignment操作符和一个析构函数。此外如果你没有声明任何构造函数,编译器也会为你声明一个default 构造函数。所有这些函数都是public 且inline 。举例,如果你写下:
这就好像你写下这样的代码:
需要注意的是,只要你显式地定义了一个构造函数(不管是有参构造函数还是无参构造函数),编译器将不再为你创建default构造函数。
条款06: 若不想使用编译器自动生成的函数,就该明确拒绝
本条款告诉程序员,如果某些对象是独一无二的(比如房子),你应该禁用copy 构造函数或copy assignment 操作符,可选的方案有两种:
(1) 定义一个公共基类,让所有独一无二的对象继承它,具体如下:
为阻止HomeForSale对象被拷贝,唯一需要做的就是继承Uncopyable:
这种方法带来的问题是,可能造成多重继承,这回导致很多麻烦。
(2) 创建一个宏,并将之放到每一个独一无二对象的private中,该宏为:
这种方法比第一种方法好,google C++编程规范中提倡使用该方法。