Effective C++第三版之(一)Accustoming Yourself to C++

Accustoming Yourself to C++.习惯C++

声明与定义
1,声明式是声明对象的名称和类型,但略去细节。
2,函数的声明表示其签名式,即参数和返回类型,等同于该函数的类型。
extern int x; //对象声明式
std::size_t numDigits(int number); //函数声明式
template <typename T> class GraphNode; //模板声明式

3,定义式是提供声明式遗漏的细节。
4,对象定义式分配内存,function或function template定义式提供代码文本,class或class template定义式列出它们的成员。
int x; //对象定义式

初始化
1,初始化是给对象赋初值的过程,用户自定义类型初始化由构造函数执行。2,default构造函数是没有参数的构造函数,或是每个参数都有缺省值,调用时可不带实参。
3,声明为explicit的构造函数禁止编绎器执行非预期的类型转换。
4,copy构造函数以同类型对象初始化自我对象,copy assignment操作符以同类型对象拷贝值到自我对象”。
5,copy构造函数定义对象如何passed by value,在pass by value调用中通常pass-by-reference-to-const是较好的选择。

STL
STL标准模板库是C++标准程序库的一部分,致力于容器、迭代器、算法及相关功能。

undefined behavior命名习惯
1,参数名称lhs, rhs,分别代表"left-hand side"和"right-hand side",以它们作为二元操作符的参数名称。
2,指向类型T的指针pT,意思是"point to T"。
3,引用references,rw意思reference to Widget。
4,成员函数,以mf为名。

条款1: View C++ as a federation of languages.视C++为一个语言联邦
1,今天的C++是个多重范型编程语言,同时支持过程形式、面向对象形式、函数形式、泛型形式、元编程形式的语言。
2,可简单将C++视为由相关语言组成的联邦:
C:C++以C为基础,区块、语句、预处理器、内置数据类型、数组、指针来源于C。但C没有模板,没有异常,没有重载。
面向对象C++:类、封装、继承、多态、virtual函数(动态绑定)等。
模板C++:C++泛型编程部分。
STL:STL模板程序库,容器、迭代器、算法以及函数对象之间的配合与协调。
3,以上四个次语言,内置类型pass-by-value通常比pass-by-reference高效,STL中迭代器和函数对象都是在C指针上塑造出来的,所以对STL的迭代器和函数对象pass-by-value高效。但面向对象C++由于自定义构造函数和析构函数的存在,pass-by-reference-to-const往往更好,Template C++同样如此。
因此,C++不是带有一组规则的一体语言,是由四个次语言组成的联邦语言,每个次语言都有自己的规约。

条款2: Prefer const, enums, and inlines to #defines. 尽量以const, enum, inline替换#define
1,#define不视为语言的一部分,宁可以编绎器替换预处理器。
#define ASPECT_RATIO 1.653
ASPECT_RATIO记号在编绎器处理源码之前被预处理器移走,记号名称ASPECT_RATIO不进入记号表symbol table内,当运用此常量但获得一个编绎错误信息时,错误信息会提到1.653而不是ASPECT_RATIO,如果ASPECT_RATIO定义在非你所写的头文件内,会因为追踪它而浪费时间,解决之道以一个常量替换。
const double AspectRatio = 1.653;
AspectRatio作为语言的常量必会被编绎器记入记号表。
2,以常量替换#define时有两种情况需要注意,一是定义常量指针,要注意指针本身为常量时如何定义;二是class专属常量,类内静态成员常量,此常量的作用域限制在class内,并确保此常量只有一份实体。
3,因编绎器的支持不同,类内静态常量的声明与定义有不同方式。
class GamePlayer {
private:
static const int NumTurns = 5;
int scores[NumTurns];

};
1)类内初值设定,仅支持整型常量。类内声明并赋初值,此为声明式而非定义式。通常C++要求必须提供定义式,但类内静态整型常量可特殊处理,只要不取它们的地址可以声明时赋初值无须提供定义式,如果需要取它们的地址或编绎器必须提供定义式时,在实现文件中提供定义:
const int GamePlayer:: NumTurns; 类内声明时已设定初值,定义时不可以再设定初值。
2)类内非整型常量,定义时设定初值。
const double GamePlayer:: dFactor = 1.35; 实现文件中定义。
3)编绎器不支持类内整型常量初值设定时,在GamePlayer:: scores的数组声明式中,编绎器必须在编绎期知道数组大小,可改用the enum hack做法,其理论基础是:枚举类型的数值可做整型使用。
class GamePlayer {
private:
enum { NumTurns = 5 };
int scores[NumTurns];

};
4,enum hack的某些行为比较像#define而不像const,取const的地址是合法的,取enum的地址不合法,取#define的地址也不合法。
1)如果不想让指针或引用指向某个整型常量,可使用enum代替const。
2)有些编绎器不会为整型const对象设定另外的存储空间,除非创建一个指针或引用指向该对象,但有些编绎器会设定另外的存储空间,而enum和#define绝不会导致非必要的内存分配。
3)enum hack是模板元编程的基础技术,后续条款涉及。
5,宏看起来像函数,但不会带来函数调用的额外开销。
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
1)以上带实参的宏必须为所有实参加上小括号,但加上小括号也可能发生意想不到的结果。
int a=5, b=0;
CALL_WITH_MAX(++a, b); //a被累加两次
CALL_WITH_MAX(++a, b+10); //a被累加一次
调用f之前,a的累加次数取决于比较结果。
2)template inline函数可获得宏带来的效率以及一般函数的所有可预料行为和类型安全性。
template<typename T>
inline void callWithMax(const T& a, const T& b)
{ f(a>b ? a : b); }
template函数可实例一整群函数,每个函数接受两个同类型对象,并以其中较大者调用f。
3)同样可以设计类内inline函数。
6,有了consts, enums, inlines,对预处理器特别是#define的需求降低了,但#include仍然是必需品,#indef/#ifndef同样需要控制编绎。
1)对于单纯变量,最好以const对象或enum替换#define
2)对形似函数的宏macros,最好用inline函数替换#define

条款3:Use const whenever possible.尽可能使用const。
1,STL迭代器是以指针为根据塑造出来的,形似T*指针,声明迭代器为const就像声明指针为const一样,迭代器不得指向其他对象,但迭代器所指对象值可以改变,声明const_iterator迭代器,所指对象值不可改变,但可指向其他对象。
2,声明const成员函数,该成员函数作用于const对象,可与相同参数和返回类型的非const成员函数重载。
1)const成员函数有两个流行概念:bitwise constness(又称physical constness)和logical constness。
2)因C++对常量性的定义,bitwise constness认为const成员函数不可以更改对象内任何non-static成员变量。这种论点很容易侦测违反点,编绎器只需要寻找成员变量的赋值动作即可。但如果指针隶属于对象,更改指针所指对象的值,可通过bitwise测试。或者函数内不更改任何non-static成员变量,但返回一个成员变量的引用,通过该函数返回的引用可改变成员变量。
3)logical constness主张const成员函数可以修改对象内mutable的成员变量,mutable释放掉non-static成员变量的bitwise constness约束。
3,const和non-const成员函数有实质等价的实现时,令non-const版本调用const版本可避免代码重复。例如类内下标运算符operator[]不单返回指向某字符的引用,也执行边界检查,数据访问,数据完善性检验等,将这些同时放进const和non-const中,其中的代码重复以及伴随的编绎时间、维护、代码膨胀等问题。
1)令non-const operator[]调用const operator[],将non-const对象类型转换成const对象表明调用const operator[],同时const operator[]返回类型为const,需要类型转换去掉const。
2)其中有两个类型转换动作,若non-const对象不类型转换成const对象去调用operator[],会递归调用自己,为了避免递归,须明确指出调用的是const operator[],因此第一次类型转换需要将*this转换成const类型,使用static_cast,第二次类型转换则是从const operator[]的返回中移除const,使用const_cast。
4,将对象声明为const可帮助编绎器侦测出错误用法,const可施加于作用域内的对象、函数参数、函数返回类型、成员函数本体。

条款4:Make sure that objects are initialized before they’re used.确定对象使用前已初始化。
1,声明一个整型变量,有些情况下默认初始化为0,有些情况却不会。
1)C part of C++,如果初始化会带来运行期成本,则不保证初始化,array(来自C part of C++)不保证内容被初始化,而vector(来自STL part of C++)保证初始化。
2)最佳处理办法:对象使用前先将它初始化。内置类型需要手动完成初始化,自定义类型对象则由构造函数完成初始化,并确保构造函数将对象的每一个成员初始化。
2,正确区分初始化和赋值,进入构造函数之前,调用成员default构造函数,此时成员变量初始化,构造函数内为赋值操作。构造函数先调用成员default构造函数再调用成员copy assignment操作符,更好的做法是使用初始值列表,初始值列表对成员进行copy构造,省略default构造函数和copy assignment操作符的调用。
3,初始值列表没有指定初值的话,自定义类型成员变量调用default构造函数初始化,内置类型初始化与赋值成本相同,不保证在赋值之前初始化,如果成员变量是const或references则必须初始化且不能赋值。所以最好是初始值列表初始化所有成员变量。
4,C++有十分固定的成员初始化次序,base classes早于derived classes,成员变量以声明次序初始化。
5,static对象,寿命从构造出来直到程序结束。
1)stack和heap-based对象除外,包括global对象、定义于namespace作用域内、classes内、函数内、file作用域内声明为static的对象,都是static对象。函数内的static对象称local static对象,其他static对象称为non-local static对象。
2)程序结束时static对象自动销毁,在main结束时自动调用析构函数。
6,non-local static对象初始化
1)C++对定义于不同编绎单元内的non-local static对象的初始化次序无明确定义。编绎单元指产出单一目标文件的源码,基本上是单一源码文件加上所含头文件。当涉及两个源码文件,每一个至少含一个non-local static对象(对象是global或位于namespace作用域内,或在class、file作用域内声明为static的对象),如果某编绎单元non-local static对象的初始化使用了另一编绎单元内的non-local static对象,而该对象很可能并未初始化。可使用单例模式解决这个问题,在函数内声明local static对象,函数返回一个reference指向所含对象,用户调用函数而不直接访问对象,local static对象替换non-local static对象,这是单例模式常见的实现手法。
2)函数内的local static对象会在函数首次调用时初始化,以函数调用替换直接访问non-local static对象可保证获得的reference指向已初始化的对象。函数未被调用时不会有构造函数和析构函数成本。
3)函数内含static对象在多线程系统中带有不确定性,处理办法是在程序的单线程启动阶段手动调用所有reference-returning函数,可消除与初始化有关的先后问题。
7,总结:
1)手动初始化内置类型对象,C++不保证初始化它们。
2)构造函数最好使用初始值列表,而不是在构造函数内使用赋值操作,初始值列表排列次序最好与声明顺序一致。
3)避免跨编绎单元初始化次序问题,以local static对象代替non-local static对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值