【M35】让自己习惯于标准C++语言
摘要:1、最近一些年C++语言增加的特性有: a、RTTI,namespace,bool,关键字mutable和explicit,enums,以及const static int可以直接初始化。 b、扩充了template的弹性,允许成员方法模版。 c、强化了异常处理机制。 d、修改了内存分配例程。2、标准程序库分为以下几项: a、支持C标准程序库。 b、支持String c、支持国别,地域化,本地化,localization。 d、支持I/O e、支持数值应用。 f、支持广泛用途的容器和算法,STL。
阅读全文
posted @
2014-04-09 16:20
Andy Niu
阅读(231)
推荐(0) 编辑
【M31】让函数根据一个以上的对象类型来决定如何虚化
摘要:1、考虑下面的问题,游戏软件中有角色A,B,角色又可以细化为A1,A2,A3;B1,B2,B3,两类角色之间相互攻击。即A1可以攻击B1,B2,B3,B1可以攻击A1,A2,A3。C++的多态,只根据方法拥有者的真实类型,决定调用哪个方法,而不关心形参的真实类型,只关心形参的表面类型。考虑方法A.Attack(B),如何根据A,B的真实类型虚化?2、使用虚方法+RTTI(运行时期类型识别),在对于A的Attack方法声明为virtual,在重写的方法中,使用typeid判断B的真实类型,决定调用哪个方法。3、只使用虚方法,在A的Attack(B)方法,调用B的AcceptAttack(A)方法
阅读全文
posted @
2014-04-09 16:03
Andy Niu
阅读(333)
推荐(0) 编辑
【M29】引用计数
摘要:1、引用计数这项技术,是为了让等值对象对象共享同一实体。此技术的发展有两个动机:a、记录堆上分配的对象,是垃圾回收机制的简单原理;b、节省内存,多个对象具有相同的值,存储多次很笨。速度更快,等值对象避免了对象复制,也就减少了构造和析构。2、考虑,基于引用计数的String,String类中有个StringValue指针,stringValue包含char指针data和引用计数refCount。注意,refCount属于StringValue,不应该属于String,否则要有多个引用计数副本,还要保持同步。3、考虑下面的问题,多个String共享同一个StringValue,如果只是读取操作,没
阅读全文
posted @
2014-04-09 16:02
Andy Niu
阅读(345)
推荐(0) 编辑
【M28】智能指针
摘要:1、什么是智能指针? 所谓智能指针就是,看起来,用起来,感觉起来都像原始指针,但是提供了更多功能。2、使用智能指针取代原始指针,可以获得更多的控制权。如下: a、在构造和析构的时候,可以做一些事。 b、在复制和赋值的时候,可以做一些事。 c、在解引用的时候,可以做一些事。3、智能指针可以对不同类型的指针进行封装,因此智能指针是一个模板类。4、智能指针对原始指针封装,内含一个原始指针,为了用起来像指针,重载->和*,->返回原始指针,*返回对象的引用,两个重载操作符都不改变智能指针,因此是const成员方法。5、考虑auto_ptr,拥有权转移,auto_ptr的copy构造会修改r
阅读全文
posted @
2014-04-03 19:48
Andy Niu
阅读(579)
推荐(0) 编辑
【M30】代理类
摘要:1、考虑二维数组,在栈上分配,必须在编译时确定大小,也就是大小是常量。另外一点,C++不支持在堆上分配二维数组。怎么解决这个问题? 二维数组可以看成,一维数组的数组。因此,可以使用代理类,Array2D的元素是Array1D,Array1D是一维数组,为了支持[][]操作,Array2D重载操作符[],Array1D重载操作符[]。2、区分operator[]的读写操作,考虑string,基于引用计数的string,赋值是浅拷贝,两个指针指向同一块内存。通过[]获取string中的字符,如果只是读取,没有关系,还可以共享。如果是写操作,必须做一个副本。因此,对于[],需要想办法区分是读操作还.
阅读全文
posted @
2014-04-03 17:02
Andy Niu
阅读(255)
推荐(0) 编辑
【M27】要求或者禁止对象产生于heap之中
摘要:1、要求对象只能产生于heap之中,该怎么办? 栈上的对象肯定调用构造方法和析构方法(离开作用域的时候),因此,要求对象只能产生于heap之中,也就是禁止栈上产生对象,解决办法有两种:将所有的构造方法声明为private,或者将析构方法声明为private。2、将所有的构造方法声明为private,这样就不能在栈上构造对象了。这有两点需要注意: a、这种情况下,不能在外部使用new operator在堆上构造对象,因为new operator要在分配的内存上,调用构造方法构造对象。因此,需要重新暴露接口,返回堆上的对象。办法有:在类内部使用new operator,暴露static方法;使..
阅读全文
posted @
2014-04-02 20:17
Andy Niu
阅读(689)
推荐(0) 编辑
【M33】将非尾端类设计为抽象类
摘要:1、考虑下面的需求,软件处理动物,Cat与Dog需要特殊处理,因此,设计Cat和Dog继承Animal。Animal有copy赋值(不是虚方法),Cat和Dog也有copy赋值。考虑下面的情况: Cat cat1; Cat cat2; Animal *a1 = &cat1; Animal *a2 = &cat2; *a1 = *a2; 思考*a1 = *a2会有什么问题? copy赋值不是虚方法,根据表面类型,调用Animal的copy赋值,这就导致所谓的部分赋值,cat2的Animal成分赋值给cat1的Animal成分,二者的Cat成分保持不变。2、怎么解决上面的问题? 将
阅读全文
posted @
2014-04-02 11:53
Andy Niu
阅读(297)
推荐(0) 编辑
【M32】在未来时态下发展程序
摘要:1、在未来时态下发展程序,就是接受“事情总会变化”的事实,并准备应对之策。2、记住,程序的维护者通常不是最初的开发者,因此,设计和实现的时候,应该考虑别人更好地理解,修改自己的程序。3、重要的一点就是,以C++语言本身来表现各种规范,而不是依靠注释或者说明文件。举例来说,如果copy构造和copy赋值没有意义,应该将他们声明为private。而不是简单地通过注释告诉用户,不要进行copy构造和copy赋值,因为用户才不管。4、让class的操作符和方法拥有自然的语法和直观的语义,和内置类型保持一致。5、记住,任何事情只要能够做,就会有人做。接受“用户会犯错”的事实。6、努力写出可移植的代码,记
阅读全文
posted @
2014-03-31 20:12
Andy Niu
阅读(244)
推荐(0) 编辑
【M34】如何在同一个程序中结合C++和C
摘要:1、C++和C混合编程的时候,需要考虑产生的目标文件的兼容性。2、名称重整,为什么要搞出名称重整? 连接器要求所有方法名必须独一无二。对于C语言,没问题。C++支持过载,也就是方法名相同,形参表不同的方法,因此编译器编译的时候,必须对方法名重整,保证方法名独一无二,满足连接器的要求。那么问题来了,C++和C混合编程,编译时没有问题,连接时出现问题了,C++进行了名称重载,而C没有,连接时名称对不上了。 怎么解决这个问题?就是使用extern C,告诉编译器,不要进行名称重整。对于C++与C共用的头文件,C++必须使用extern C,而C语言不识别extern C,因此使用预编译。对于__c.
阅读全文
posted @
2014-03-31 18:54
Andy Niu
阅读(545)
推荐(0) 编辑
【M25】将构造方法和非成员方法虚化
摘要:1、所谓虚化,就是根据引用或者指针的真实类型,决定调用哪个方法。2、构造方法虚化,就是根据引用(或者指针)的真实类型,构造出一个对象,如果指针的真实类型是Base,返回Base*;如果指针的真实类型是Derived,返回Derived*。解决办法是:Base定义一个virtual方法Clone,调用new Base(*this),返回Base*;Derived重写Clone方法,调用new Derived(*this),返回Derived*。注意:一般情况下,子类重写父类方法,要求返回类型必须一致。目前,父类返回Base*,子类重写可以返回Derived*,也就是说C++支持协变。(口诀:进去
阅读全文
posted @
2014-03-25 19:56
Andy Niu
阅读(282)
推荐(0) 编辑
【M23】考虑使用其他程序库
摘要:1、程序库的设计是一种权衡的结果。体积小,速度快往往不能移植。可移植,通用的程序库往往意味着冗余和效率低下。2、因此,选择程序库的时候,需要进行取舍。比如:iostream和stdio。iostream具有类型安全性,可扩充,而stdio的效率更高。注意,对性能评估的时候,不要猜,应该进行多次实验进行确认。
阅读全文
posted @
2014-03-25 18:45
Andy Niu
阅读(169)
推荐(0) 编辑
【M18】分期摊还预期的计算成本
摘要:1、基本思想就是:如果将来肯定要做某件事,并且这件事情耗时,提前把东西准备好,先做一部分。常用的使用场景有:2、考虑一个大的数据集合,集合中元素不断变化。经常要取出里面的最大值,正常的做法是:每次调用的时候,计算出最大值,这可能是一个耗时的操作。既然是经常取出最大值,那我就实时(每次对集合增删改的时候)更新最大值,需要的时候直接就返回最大值了。3、考虑stl中的vector,vector有5个元素,假设vector的内存刚好容纳5个元素,现在增加一个元素。那就意味着必须分配一块6个元素的内存,把原来的5个元素和新增的1个元素copy过来。这显然效率很低。解决办法是:预留一些内存。4、较快的速度
阅读全文
posted @
2014-03-25 18:20
Andy Niu
阅读(420)
推荐(0) 编辑
【M17】考虑使用缓式评估
摘要:1、缓式评估其实就是拖延战术,直到逼不得已的时候才去计算。缓式评估的使用场景有:2、引用计数,考虑String,String是一个内含char指针(char指针以'\0'结束)的资源管理类,正常情况下,String的copy构造和copy赋值都是深层copy,也就是对char指针指向的内容做一个副本,这个效率显然很低。考虑String s2 = s1; 后面的代码可能只是读取s2,没有必要做深层copy,s2与s1可以共享一份数据,也就是使用引用计数,来实现String。但是,当修改String的时候,必须做一个深层copy,也就是拖延战术。3、区分读与写,考虑cout<
阅读全文
posted @
2014-03-25 17:59
Andy Niu
阅读(448)
推荐(0) 编辑
【M24】了解虚方法、多继承、虚基类、RTTI的成本
摘要:1、编译器必须实现出C++语言的特性。一般情况下,我们只需要使用这些特性就好了,不需要关心内部的实现细节。但是,有些特性的实现,会对对象的大小和成员方法的执行速度造成影响。因此,有必要了解内部实现的细节。2、首先考虑虚方法,虚方法是用来实现多态的。多态是指对于指针和引用,表面类型和真实类型不一致的情况下,调用真实类型的虚方法。3、虚方法有关的实现细节为: a、父类有一个虚方法表(vtbl),可以认为是一个方法指针的数组(这里注意:对于数组,我们知道元素的类型必须一致,虚方法表中的虚方法类型是不一样的,这里进行了特殊处理),方法指针指向父类的虚方法。 b、子类整体拷贝父类的虚方法表,对于重写..
阅读全文
posted @
2014-03-12 18:00
Andy Niu
阅读(607)
推荐(0) 编辑
【M14】明智运用异常规范
摘要:1、异常规范的使用场景是,承诺方法只抛出什么样的异常,或者不抛出异常。如果运行的时候,不满足承诺,C++自动调用unexpected方法,unexpected调用terminate方法,terminate调用abort方法结束程序。2、有三点需要注意:a、表面上不抛出任何异常,仔细分析还是可能会抛出异常;b、调用其他的方法,而其他的方法可能抛出异常;c、调用系统的方法,系统方法可能抛出异常。因此,承诺只抛出某些异常,或者不抛出异常(异常规范),基本上不太靠谱。3、如果抛出了,非预期的异常。默认情况下,调用unexpected-->terminate-->abort,导致程序结束。因
阅读全文
posted @
2014-03-06 20:35
Andy Niu
阅读(374)
推荐(0) 编辑
【M11】禁止异常流出析构方法之外
摘要:1、在两种情况下,调用析构方法:a、在正常状态下被销毁,栈上的对象离开作用域或者堆上的对象执行delete;b、抛出异常,堆栈回滚,栈上已经构造好的对象,也就是抛出异常之前的代码,自动调用析构方法。注意:只会对已经构造好的栈上对象调用析构方法,而不会对已经初始化好的指针执行delete,因此,使用智能指针可以避免这种情况的资源泄漏。2、考虑下面的情况,析构方法中抛出异常,在外部捕获异常。如果是正常情况下调用析构方法,没有问题。如果由于异常,堆栈回滚对栈上已经构造好的的对象调用析构方法,这个时候析构方法又抛出一个异常,导致C++调用terminate方法,结束程序。3、析构方法抛出异常,还有另外
阅读全文
posted @
2014-03-06 20:05
Andy Niu
阅读(368)
推荐(0) 编辑
【M10】在构造方法内阻止资源泄漏
摘要:1、类中没有指针,如果对象构造过程中出现异常,C++保证已经构造好的那一部分自动销毁。注意:这里不是调用析构方法,而是编译器在你的构造方法中插入了一些代码,保证对已经构造好的对象析构。2、类中有指针,比如有a, b两个指针。对象构造时,a初始化完成,b抛出异常,C++只会对已经构造好的对象析构,而不会对已经初始化好的指针执行delete。因此资源泄漏。3、注意,这种情况下,绝不会调用析构方法,因为C++只会析构已经构造完成的对象。接着思考,在堆上分配对象,把指针传出来,对指针delete,可行吗? 不可行,因为构造过程出现异常,指针是传递不出来的。外部的指针还是null。4、思考:为什么C+.
阅读全文
posted @
2014-03-03 20:32
Andy Niu
阅读(418)
推荐(0) 编辑
【M9】利用destructors避免泄漏资源
摘要:1、在堆上获取的动态资源,用户忘记delete,或者由于异常导致没有没执行到delete,都会造成资源泄漏。2、我们知道,栈上的对象,离开作用域,必定要执行析构方法。即使抛出异常,会堆栈回滚,保证已经构造的对象进行析构。3、因此,可以使用栈上的对象,管理资源,在析构方法中释放资源,保证不会资源泄漏。4、考虑更一般化的资源,对于互斥体的加锁,解锁,也是同样的情况,需要保证互斥体一定会解锁。5、对资源管理的类,就是智能指针。因此,需要对不同类型的对象进行管理,智能指针是模板类,在栈上分配,行为像指针。常用的智能指针有auto_ptr,shared_ptr。注意,智能指针默认的删除动作是delete
阅读全文
posted @
2014-03-03 19:49
Andy Niu
阅读(445)
推荐(0) 编辑
【M5】对定制的“类型转换函数”保持警觉
摘要:1、隐式类型转换有两种情况:单个形参构造方法和隐式类型转换操作符。注意:隐式类型转换不是把A类型的对象a,转化为B类型的对象b,而是使用a对象构造出一个b对象,a对象并没有变化。2、单个形参构造方法包括两种情况:声明只有单个形参;或者声明有多个形参,但是除了第一形参,其他的形参都有默认值,也就是说,只要单个形参就能构造对象。 注意:默认形参必须从右向左进行。思考为什么? 调用方法的时候,从左到右使用实参初始化形参,没有提供实参,就使用默认形参值,因此默认形参必须从右向左进行。比如,有5个形参,后面三个有默认值,调用方法的时候,提供三个实参,后面两个使用默认值。3、隐式类型转换操作符,是一种..
阅读全文
posted @
2014-03-03 19:09
Andy Niu
阅读(307)
推荐(0) 编辑
【M4】非必要不提供default 构造方法
摘要:1、default 构造方法意味着,没有外来信息的情况下,进行初始化,构造出一个对象。对于有些对象是很合理的,比如数值之类的对象,可以初始化为0;对于指针之类的对象,初始化为null;对于集合如vector,list,可以初始化为一个空容器。2、对于有些对象,必须要有外来信息,才能构造出一个有意义的对象。比如Person,要有name才能构造出一个有意义的Person对象。3、因此,对于没有外来信息,也能合理构造出一个对象的类,必须提供default 构造方法。对于没有外来信息,不能构造出一个对象的类,不能有default构造方法。4、现在考虑,没有default构造方法,使用类会有哪些限制?
阅读全文
posted @
2014-03-02 18:52
Andy Niu
阅读(592)
推荐(0) 编辑