More Effective C++
扮猪吃饺子
努力的人,运气不会太差。
展开
-
1 指针和引用的区别
任何情况下都不能使用指向空值的引用。char* pc = 0;//设置指针为空值char& rc = *pc;//让引用指向空值引用必须被初始化。string& rs;//引用必须被初始化string s("xyz");string& rs = s;//正确,rs指向ssting *ps; //未初始化的指针合法单危险 不存在指向空值的引用这个事实意味着使用引用的代码效率比...原创 2018-07-09 10:59:11 · 238 阅读 · 0 评论 -
14 审慎的使用exception specifications
编译器在编译的时有时能检测到异常规格的不一致,而且一个异常函数抛出一个不在异常规格范围内的异常,系统在运行时能够检测到这个错误,然后特殊函数unexpected将被自动调用,终止程序,导致一场灾难。 我们很容易编写出这种导致灾难的函数。编译器仅仅部分的检测异常的使用是否与异常规格一致。一个函数调用另外一个函数 ,并且后者可能抛出一个违反前者的异常规格的异常,(A函数调用了B函数,但B函数可...原创 2018-07-23 09:48:35 · 313 阅读 · 0 评论 -
21 通过重载避免隐式类型转换
如下一段代码,没比较寻常的:class UPInt{public: UPInt(); UPInt(int value); ...};const UPInt operator+(const UPInt* lhs,const UPInt* rhs);UPInt upi1,upi2;...UPInt upi3 = upi1 +upi2;现在考虑如下代码:upi3 ...原创 2018-07-31 09:41:46 · 219 阅读 · 0 评论 -
22 考虑使用运算符的复合形式取代其单独形式
对于如下的代码:x = x + y; x = x - y;也可以这样写:x += y; x -= y; 如果x和y是用户自定义类型,就不能确保这样。就C++来说,operator+、operator=和operator+=之间没有任何关系,因为如果你想让这三个operator同样存在并具有你所期望的关系,你必须自己实现它们。同理operator-,*,/,等等一样。...原创 2018-08-01 09:40:20 · 168 阅读 · 0 评论 -
24 理解虚函数、多重继承、虚基类和RTTI的代价
当调用一个虚函数时,一般都是使用了virtual table和virtual table pointer,这两个简称vtbl和vptr。一个vtbl通常是一个函数指针数组。在程序中每个类声明了虚函数和继承了虚函数,它就与自己的vtbl,并且类中的vtbl的是指向虚函数实现体的指针。例如:class C1{public: C1(); virtual ~C1(); virtual ...原创 2018-08-06 11:23:04 · 282 阅读 · 0 评论 -
23 考虑变更程序库
程序库的设计是一个折中的过程。理想的程序库应该短小、快速、强大、灵活、可扩展性、直观、试用、良好的支持、没有使用约束、没错误等等。真实的世界里。你不能拥有每一个优点,总的付出代价。不同的设计给予这些条件赋予了不同的优先级。他们从而在设计中牺牲了不同的东西。因此一般两个提供相同功能的程序库却有着完全不同的特征。例如,考虑iostream和stdio程序库。对于C++程序员来说两者都可以使用。...原创 2018-08-02 09:57:04 · 140 阅读 · 0 评论 -
26 限制某个class所能产生的对象数量
允许零个或一个对象每当即将产生一个对象,我们确知一件事情:会有一个constructor被调用。“阻止某个class产出对象”的最简单方法就是将其constructorsh声明为private:class CantBeInstantiated{private: CantBeInstantiated(); CantBeInstantiated(const CantBeInstanti...原创 2018-08-15 21:49:04 · 273 阅读 · 0 评论 -
25 将constructor和non-member functions虚化
假如你写一个软件,用来处理时事新闻,其内容由文字和图形构成。你可以把程序组织成这样:class NTComponent{ //抽象基类,用于时事消息的组件public: //其中至少含有一个纯虚函数 ...};class TextBlock:public NTComponent{public: ... //没有内涵任何纯虚函数}; class Graph...原创 2018-08-08 21:18:28 · 210 阅读 · 0 评论 -
27 要求(或禁止)对象产生于heap之中
有时候,我们要求该类型的对象被分配与heap内,能够“delete this”;另一些时候,我们要求拥有某种确定性,保证某一些类型绝不会发生内存泄漏,原因是没有任何一个该类型的对象从heap中分配出来。要求对象产生于heap之中(所谓的Heap-Based Objects)为了限制对象产生于heap,我们必须找到一个方法,阻止clients不得使用new以外的方法产生对象。non-heap ...原创 2018-08-22 21:08:35 · 364 阅读 · 0 评论 -
28 Smart Pointers(智能指针)
所谓的smart pointers,是“看起来、用起来、感觉起来像内置指针,但提供更多的机能”的一种对象。smart pointers拥有以下各种指针行为的控制权: 构造和析构(Construction and Destruction) 复制和赋值(copying and Assignment) 解引(Dereferencing) Smart pointers由t...原创 2018-08-27 21:24:49 · 555 阅读 · 0 评论 -
29 Reference counting(引用计数)
Reference counting这项技术有两个动机:第一,是为了简化heap objects周边的簿记工作,reference counting建构出垃圾回收机制的一个简单形式。第二,是为了实现一种常识。如果许多对象有相同的值,将那个值存储多次时间愚蠢的事情。没用Reference counting,首先看下如何造成许多同值对象,下面是一种可能:class String //标准的...原创 2018-09-02 10:32:17 · 695 阅读 · 0 评论 -
30 Proxy classes(替代类、代理类)
实现二维数组C++中如何支持多维数组:产生一个class,用以表现我们需要却被语言遗漏的对象,我们可以这样定义一个class template:template<class T>class Array2D{public: Array2D(int dim1,int dim2); ...};我们可以这样定义数组:Array2D<int> data(...原创 2018-09-03 14:34:29 · 453 阅读 · 0 评论 -
32 在未来时态下发展程序
好的软件对于变化有良好的适应性。好的软件可以容纳新的性质,可以移植到新的平台,可以适应新的需求,可以掌握新的输入。所谓的未来时态下设计程序,就是接受“事情总会改变”的事实,并准备应付之道。程序的维护者通常不是当初的开发者,所以设计和实现时应该注意到如何帮助其他人理解、修改、强化你的程序。未来式思维只不过是加上一些额外的考虑:提供完整的classes——即使某些部分目前用不到。 设计你的接...原创 2018-09-08 16:26:15 · 173 阅读 · 0 评论 -
34 如何在同一个程序中结合C++和C
首先,你得确保你的C++和C编译器产生兼容的目标文件。然后另外4件事情需要考虑:name mangling(名称重整)、statics(静态对象)初始化、动态内存分配、数据结构的兼容性。Name Mangling(名字重整)C++编译器为程序内的每一个函数编出独一无二的名字,在C语言中,却不会这样做,因为你无法将函数名重载。重载不兼容于大部分连接器,因为连接器往往将多个同名函数视为不正常...原创 2018-09-11 20:33:34 · 370 阅读 · 0 评论 -
35 让自己习惯标准C++语言
C++最重要的几项标准:增加了一些新的语言特性:RTTI、namespaces、bool、关键字mutable和explicit、enums作为重载函数之自变量引发的类型晋升转换,以及“在class定义区内直接为整型 const static class members设定初值”的能力。 扩充了templates能力。 强化了异常处理机制。 修改了内存分配例程。 增加了新的转换形式。...原创 2018-09-12 08:06:42 · 171 阅读 · 0 评论 -
31 让函数根据一个以上的对象类型来决定如何虚化
对于一个视频游戏软件,涉及到宇宙飞船、太空站,小型星是否存在碰撞的风险。先写一个继承类: class GameObject{...};class SpaceShip: public GameObject{...};class SpaceStation: public GameObject{...};class Asteroid: public GameObject{...};...原创 2018-09-06 21:10:25 · 258 阅读 · 0 评论 -
19 了解临时对象的来源
C++真正的所谓的临时对象是不可见的——不会再你的源码中出。只要你产生一个non-heap object而没有为它命名,便会产生一个临时对象。匿名对象通常发生于两种情况:一是隐式类型转换被实施起来以求函数调用成功;二是当函数返回对象的时候。 首先靠考虑为了让函数调用成功”而产生的临时对象。此乃发生于“传递某对象给一个函数,而其类型和它即将绑定上去的参数类型不同”的时候。如下代码,考...原创 2018-07-27 09:34:27 · 198 阅读 · 0 评论 -
20 协助完成返还值最优(RVO)
一个返回对象的函数很难有较高的效率,因为值传递返回会导致对象的构造和析构的成本,这种调用不可避免。考虑rational(有理数)类的成员函数operator*:class Rational{public: Rational(int numerator = 0,int denominator = 1); ... int numerator() const; int deno...原创 2018-07-30 09:38:57 · 171 阅读 · 0 评论 -
9 利用destructor避免资源泄露
例如:你正在为“小动物收养保护中心”编写软件。收养中心每天产生一个文件,其中有它所安排的当天的收养个案。你的工作就是写一个程序,读这些文件,然后为每一个收养个案做适当处理。class ALA {public: virtual void processAdoption() = 0; ...};class Puppy: public ALA{public: void processAdoption(...原创 2018-07-16 09:34:56 · 226 阅读 · 0 评论 -
2 尽量使用C++风格的类型转换
C风格转换的缺点:它们过于粗鲁,能允许你在任何类型之间转换。C风格的类型转换在程序中难以识别。C++风格的类型转换:static_cast 、const_cast、dynamic_cast和reinterpret_castC风格的写法:(type)expressionC++风格的写法:static_cast<type>(expression)例如,假如你想把一个int转换成double...原创 2018-07-09 11:00:07 · 151 阅读 · 0 评论 -
3 绝不要以多态方式处理数组
假设你有一个类BST(比如是搜索树对象)和继承自BST类的派生类BalancedBST :class BST{};class BalancedBST: public BST{};有这样一个函数,它打印出BST数组的没一个BST的内容:void printBSTArray(ostream& os,const BST array[],int numElements){ for(int ...原创 2018-07-10 10:06:15 · 203 阅读 · 0 评论 -
4 非必要不提供default constructor
考虑下面的针对公司仪器设计的class,仪器识别码是一定的有的一个constructor自变量:class EquipmentPiece{public:EquipmentPiece(int IDNumber);...};1.第一种情况,在产生数组的时候,一般而言没有任何方法可以为数组中的对象指定constructor自变量。所以几乎不可能产生一个由EquipmentPiece objec...原创 2018-07-10 10:08:29 · 345 阅读 · 0 评论 -
10 在constructor内阻止资源泄露
想象你正在开发一个多媒体通信薄软件,这个软件可以放置人名、地址、电话号码等文字,以及一张个人相片和一段个人声音(或许是其姓名的发音)。 设计如下:class Image{public: Image(const string& imageDataFileName); ...};class AudioClip{public: ...原创 2018-07-17 09:53:27 · 188 阅读 · 0 评论 -
5 对定制的“类型转换函数”保存警觉
有两种函数允许编译器进行这些的隐式转换:单参数构造函数和隐式类型转换运算符。单参数构造函数是指只用一个参数既可以调用构造函数,该函数可以只定义了一个参数,也可以定义了多个参数但第一个参数以后的所有参数都有缺省值。例子如下:class Name{public: Name(const string& s); //string转换成Name ...};class Rational...原创 2018-07-11 09:44:30 · 142 阅读 · 0 评论 -
6 区别 increment或decrement操作符的前置(prefix)和后置(postfix)形式
C++的increment或decrement前置和后置区别是:让后置放置哑元,前置返回引用,后置返回const对象。例如:class UPInt{public: UPInt& operator++(); const UPInt operator++(int); UPInt& operator--(); const UPInt operator--(int); ...原创 2018-07-11 09:45:37 · 292 阅读 · 0 评论 -
7 千万不要重载 与,或 还有 逗号 操作符
和C一样,C++对于“真假表达式”采用的是“骤死式”(短路求值法)评估模式。一旦该表达式的真假值确定,即时表达式还有部分尚未检验,整个评估工作仍结束。例如:char* p;...if((p != 0) && (strlen(p) > 10))你无须担心调用strlen时p是否为null指针,因为如果“p是否为0”的测试结果是否定的,strlen绝不会被调用。同样的道理,以...原创 2018-07-12 09:37:07 · 286 阅读 · 0 评论 -
11 禁止异常流出destructor之外
两种情况下destructor会被调用:当对象在正常状态下被销毁,也就是当它离开了它的生存空间(scope)或是被明确地删除; 当对象被exception处理机制——也就是exception传播过程中的stack-unwinding(栈展开)机制——销毁。 如果控制权基于exception的因素离开destructor,而此时正有另外一个exception处于作用状态,C++会调用te...原创 2018-07-18 09:43:11 · 255 阅读 · 0 评论 -
15 了解异常处理的开销
一般采用不支持异常的方法编译比支持异常的的程序运行速度更快所占空间更小,我们知道异常会带来开销,却很难预测出开销的准确数量。 不论异常处理的开销多大我们都坚持只有必须付出时才付出的原则。为了使你的异常开销最小化,只有尽可能的采用不支持异常的方法编译程序,把使用try块和异常规格限制在你确定需要它们的地方,并且只有在确为异常的的情况下抛出异常。如果你在性能上仍旧有问题,总体评估下一下你的软件...原创 2018-07-24 09:48:27 · 290 阅读 · 3 评论 -
16 牢记80-20准则
80-20准则:大约20%的代码使用了80%的资源。当程序员力争最大化提升软件的性能的时候,80-20准则既简化了你的工作又使你的工作变得复杂。一方面80-20准备表示大多数时间你能够编写性能一般的代码,因为80%的时间里这些代码的效率不会影响整体的性能,这些减少一些你的工作压力。另一方面这条准则也表示如果你的软件出现了性能问题,你将面临一个困难的工作,因为你不仅必须找到导致问题的那一小块...原创 2018-07-24 09:49:11 · 240 阅读 · 0 评论 -
8 了解各种不用意义的new和delete
当你写下面这样的代码:string *ps = new string("Memory Management"); //new操作符总是做相同的事情:1、它分配足够的内存,用来存放对象。2、它调用constructor,为刚才分配的内存中的那个对象设定初值。你能够改变的是用来容纳对象的那块内存的分配行为。这个名称叫做operator new。函数声明如下:void operator new(size...原创 2018-07-13 09:40:14 · 218 阅读 · 2 评论 -
12 了解“抛出一个exception”与“传递一个参数”或“调用一个虚函数”之间的差异
函数参数和exception的传递方式有三种:by value,by reference,by pointer。然而视你所传递的是参数或exception,发生的事情完全不同。原因:当你调用一个函数,控制权最终会回到调用端(除非函数失败以至于无法返回),但当你抛出一个exception,控制权不会再回到抛出端。有这样的一个函数,参数类型是Widget,并抛出一个Widget类型的异常:...原创 2018-07-19 13:34:26 · 1340 阅读 · 0 评论 -
17 考虑使用lazy evaluation
当你采用lazy evaluation(缓式评估)后,采用此方法的类推迟计算工作知道系统需要这些计算结果,如果不需要,将不进行计算。 lazy evaluation广泛用于各种应用领域,比如如下四种情况:引用计数 class String{}; //string类String s1 = "Hello";String s2 = s1;通过string拷贝赋值函数让啥s2...原创 2018-07-25 09:53:50 · 400 阅读 · 0 评论 -
13 通过reference捕获异常
捕获异常的方法有三种:通过指针、通过传值和引用。 首先通过指针捕获异常(catch by pointer),通过指针方法效率最高,因为在传递异常时,通过指针抛出异常的方法不会拷贝对象:class exception{...}; //来自C++标准void someFunction(){ static exception ex; ... throw &ex; ...原创 2018-07-20 09:46:33 · 309 阅读 · 0 评论 -
18 分期摊还期望的计算
核心条款——over-eager evaluation(过度热情计算法) :在要求你做某些事情之前就完成它们。例如下面模板类,用来表示大量数字类型的集合:template<class NumericalType>class DataCollection{public: NumericalType min() const; NumericalType max() co...原创 2018-07-26 09:40:51 · 312 阅读 · 0 评论 -
33 将非尾端类(non-leaf classes)设计为抽象类(abstract classes)
假设你正在进行一个项目,用软件来处理动物。只有两种动物:蜥蜴和鸡,如下是组织形式:Animal class 将负责你所处理的所有动物的共同特征具现化,Lizard和Chicken class则分别将Animal特殊化为蜥蜴和鸡:class Animal{public: Animal& operator = (const Animal& rhs); ...};...原创 2018-09-10 21:10:58 · 489 阅读 · 0 评论