闲话:C++程序员分为两类:读过Effective C++和没读过的。
条款1:视C++为语言联邦
过程、对象、泛型、元编程、函数式;
对内置类型而言,pass-by-value通常比pass-by-reference高效;
对于用户自定义的对象,由于构造和析构的存在,pass-by-reference-to-const往往更好;
对于小的用户定义类型,也不一定是pass-by-value好,因为考虑到以后的变化(版本更新);还有编译器不一定把内建类型和自定义的一视同仁,即使只包含一个double;
条款2:const、enum、inline替换#define
#define MAX(a,b) func((a)>(b)? (a):(b)),如果是++a,则会累加两次;可以考虑内联函数解决;
条款3:尽量使用const
1.const修饰指针,const point / const data;
2.STL迭代器: const std::vector
3.函数返回常量时,可以用const修饰,保证安全;
4.const修饰成员函数,函数参数不可变;
5.const修饰成员函数的返回值;
条款4:对象使用前初始化
1.永远在使用对象前初始化,内置类型也必须手动初始化;
2.成员变量的初始化发生在进入构造函数本体前;
3.在初始化列表初始化所有成员变量,如果是const或reference,就必须初始化;
初始化动作是发生在进入构造函数之前的。
Class A { Public: A(int a) _a(a) { //这里不是初始化 } Private: int _a; }
对象实例单一时,使用单例模式;
条款5:编译器自己会创建默认默认构造函数、copy构造函数、赋值运算符
但是对于内含reference成员或const的class,需要自定义copy assignment函数;
条款6:可以明确告诉编译器不要生成默认的赋值构造函数
方法:自己声明private的copy构造函数和copy assignment构造函数,这样能够避免别人调用时编译器自动生成;
或者继承uncopyable,这样子类就不能使用copy构造函数和copy assignment构造函数,优点是错误提前到编译期;(看书理解)
条款7:多态基类声明virtual析构函数
如果需要在运行期决定需要用哪个对象或者virtual函数,则需要virtual这个特性(即带多态性质的);
防止内存泄漏,只释放了基类而没有释放继承对象的内存;
虚函数表、指针vptr;
每一个带有virtual函数的类class都有一个vptl;(注意:是类!非对象)
增加了对象体积;
继承STL容器时,尤其注意这些容器都是non-virtual的;
纯虚函数与抽象类;
条款8:别让异常逃离析构函数
捕捉异常;并调用一个函数处理该异常;
Dbconn::~Dbconn(){Try...catch...}
条款9:不在构造和析构函数中调用virtual函数
先构造基类时,执行的虚函数是基类的虚函数,而不是子类的虚函数
条款10:重载operator=返回一个*this
Obj A;
A = B = C;
条款11:在operator=中处理自我赋值(copy and swap)
Class A{ Private: B* pb; } A& A::operator=(const A& A1) { if(this == &A1) return *this;//去掉这一句会异常,因为自我赋值时pb指针为空 Delete pb; Pb = new B(*A1.pb); Return *this; }
copy and swap;//先成功造出来,再迁移(P56)
widget temp(rhs);
swap(temp);
条款12:复制对象时勿忘每一个成分***
复制每一个local成员变量;
尤其是在子类的复制的时候,不要忘记父类;
Copy构造函数的初始化列表加上:Father(son)
赋值函数加上:Father::operator=(son);
copy assignment不要调用copy构造函数,因为这相当于构造一个已经存在的对象;
资源管理
资源管理类很重要!经常用到智能指针
条款13:以对象管理资源
资源取得时机便是初始化时机;
智能指针特性:调用复制函数时,原来的智能指针指向空,这样保证了指针对对象唯一的拥有权
Share_ptr替代了auto_ptr,引入了计数机制,无人指向的时候销毁资源
条款14:资源管理类中小心copy行为
例如:自定义一个锁类LOCK,如果lock对象被复制,则会发生两次析构,异常发生。
解决办法:禁止copy、引入share_ptr、深度拷贝、控制权转移;
条款15:资源管理类中提供对原始资源的访问
例如:你引入了智能指针shared_ptr
func(pInv.get()),取得原始指针。
还有一个隐式转换
class Font{ public: operator FontHandle ()const{return f;}//隐式转换 …… Private: FontHandle f; }; Font f1(getFont()); FontHandle f2=f1;//发生了隐式转换
条款16:成对使用new和delete时形式要相同
new一个对象时,delete一个对象;new []数组时,delete[]数组;
条款17:以独立语句将new置入智能指针中
就是先执行:std::tr1::shared_ptr pw(new A);
然后调用函数: processA(pw,priority());
不要直接这样做:processA(new A,priority());因为不能隐式转换;
还有另一种情况,资源泄漏,看书吧!
C++中的explicit关键字只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示的, 而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式)。
设计与声明
条款18:让接口容易被正确使用,不易被误用
日期接口对比:Date(int month,int day,int year);
Date d(13,20,2018);//error
用函数代替对象
Date d(Month::Feb(),Day(20),Year(2018))
不要过于依赖人力因素,很多时候通过代码设计避免问题
例如:shared_ptr
智能指针夹带删除器,结束时会自动调用删除器
条款19:设计class犹如设计type
意思就是要谨慎设计,像当初语言内置类型设计一样
注意:1、对象的创建和销毁
条款20:用引用传递替换值传递
调用函数时,用引用传递替换值传递,明显减少构造和析构次数,提高程序效率;如果不希望传入对象被修改,加入const。
还有对于有继承关系的类,如果值传递一个子类的对象,则会发生对象切割,子类的函数没有被调用,而是调用了父类的函数;改成引用传递就不会。
对于内置类型、stl迭代器和函数对象,值传递好些。
条款21:必须返回对象时,别妄想返回reference
有时候是不能返回ref的,因为可能存在资源提前释放或泄露问题
W=x*y*z,*运算符,最好返回普通对象,否则资源提前释放
条款22:成员变量声明为private
实现封装。
Public变量变化,所有客户码都要变化;
Protect变量变化,只有子类代码需要变化
条款23:以non-member、non-friend替换member函数
条款24:若所有参数需要类型转换,采用non-member函数
Res= x*2; res=2*x;第二个执行失败,第一个成功,因为2是int类型,与x类型的*运算不相同;
所以声明一个non-member函数,就能解决问题
条款25:写一个不抛异常的swap
传统swap速度慢,中间有copy函数和=运算符
总的来说就是:利用新类保证原来数据类的指针,新类提供member函数swap(b)用于指针交换;同时为了用户使用简易性,同一个命名空间加入swap(a,b)的non-member函数
详细看该链接:
http://blog.csdn.net/u011058765/article/details/46663827
考虑写出一个不抛出异常的swap函数
本节讲解如何自定义一个高效的swap函数
对于std名空间中的swap缺省函数如下所示
namespace std{ template
class WidgetImpl{ public: …… private: int a,b,c; //数据很多,复制意味时间很长 std::vector
· 我们可以看到,当交换对象的数据很大时,以上swap的执行效率会很慢,为了改变这种状况,作者提出以下方法;
重新定义一个类
class Widget{ public: Widget(const Widget& rhs); Widget& operator=(const Widget& rhs { …… //复制Widget时,复制WidgetImpl对象 *pImpl=*(ths.pImpl); …… } …… private: WidgetImpl* pImpl;//指针,含有Widget的数据 };
如果置换两个Widget对象值,只需要置换其pImpl指针,那么效率就会很高。
那么怎样定义swap呢?
如下:
calss Widget{ public: …… void swap(Widget& other) { using std::swap;//这个声明有必要 swap(pImpl, other.pImpl); } …… }; namespace std{ template<> //修订后的swap版本 void swap
从上面的代码我们可以看出完成了我们的目的,实现两个WidgetImpl对象交换,只交换他们的指针。当然这里需要构造出一个交换类Widget,需要全特化Widget 类型的swap,需要在函数体内定义member函数swap。
那么,读者就迷茫了,绕了一大圈,怎么不直接调用成员函数swap,这是因为当你用swap(a,b)语句时,它会拥有自动合适匹配的功能。更适合客户使用,实现接口的一致性和简易性。
下面我们来介绍当两个类都是template,如何解决上面的问题?
如下:
template
我们可以重新命名一个名空间如下:
namespace WidgetStuff{ ……//模板化的WidgetImpl等 template
5实现
条款26:尽可能延后变量定义式的出现时间
1.延后直到需要使用该变量,最好能将它初始化。
2.对于循环的变量定义,
定义在外面:1次构造和1次析构、n次赋值
定义在里面:n次构造和n次析构
条款27:尽量少做转型动作
C++新式转型
const_cast
将对象常量性转型
dynamic_cast
安全向下转型 继承体系,实现Base* to derived*
Reinterpret_cast_cast
低级转型 ,int* to int
static_cast
强迫隐式转换 例如:void* -》typed指针、基类指针-》子类指针
尽量避免转型,特别是dynamic_casts;如果转型是必要的,将其隐藏在函数背后;使用新式转型,不用旧式转型;
条款28: 避免返回handles指向对象内部的成分
handler(包括reference、)
带来危险,可能会修改到对象的private变量;
有时候返回了引用,可以修改对象的内部数据
条款29:为异常安全而努力是值得的
考虑出现异常的情况,因为异常一旦出现,你的资源可能泄露,数据丢失
Copy and swap,先做副本,修改,成功后swap
条款30:透彻了解inline
热衷使用inline造成程序体积过大,增加换页行为、缓存命中降低
小型、被频繁调用的函数可以用inline
条款31:将文件间的编译依存关系降至最低
使用类的前置声明
实现类用指针指向
1.接口与实现分离 pimpl手法
2.纯虚基类、接口类、抽象基类
6 继承和面向对象设计
条款32:确定你的public继承塑模出is-a关系
子类以public继承基类,告诉了编译器,每一个子类对象同时都是基类的对象。
Person和student,student is a person;
使用public继承时, base类中的每个方法都能在derived上使用;
矩形与正方形;
has-a、is-implemented-in-terms-of。
条款33:避免遮掩继承而来的名称
实际运作:子类作用域套嵌在基类的作用域内
子类的函数名称会遮盖基类的同名函数
引入using base::func,仍然可以使用基类的函数
或者直接base::func
条款34:区分接口继承和实现继承
纯虚函数目的是被子类继承当函数接口,子类必须实现它;
非纯虚函数可以被继承,缺省实现
非虚函数:继承接口,强制实现
条款35:考虑virtual函数以外的其他选择
NVI手法、template method设计模式
用一个函数包装一个被子类继承的虚函数。
class GameCharacter{ public: int healthValue() const { …… //做事前工作 int retVal=doHealthValue();//真正做实际工作 …… //做事后工作 return retVal; } …… private: virtual int doHealthValue() const //derived classes可以重新定义 { …… } };
Strategy模式
class GameCharacter;//forward declaration int defaultHealthCalc(const GameCharacter& gc);//健康计算缺省算法 class GameChaaracter{ public: typedef int(*HealthCalcFunc)(const GameCharacter&); explicit GameCharacter(HealthCalcFunc hcf=defaultHealthCalc) :healthFunc(hcf) {} int healthValue()const { return healthFunc(*this); } …… private: HealthCalcFunc healthFunc;//函数指针 };
藉由tr1::function完成Strategy模式
策略模式:把算法用一个类包装起来
这样传入的就可以不一定是函数指针了,还可以是函数对象、成员函数等等(泛化的指针)
typedef std::tr1::function
条款36:绝不重新定义继承而来的non-virtual函数
non-virtual函数是静态绑定的,virtual才是动态绑定
条款37:绝不重新定义继承而来的缺省参数值
缺省参数值是静态绑定的!子类继承父类的virtual函数,默认参数依然是父类的默认参数,而不是子类的默认参数。
那如果子类的虚函数想用默认参数,那不就用不了?!
解决办法:用NVI手法,用non-virtual函数包装virtual函数
条款38:通过复合塑模出has-a或“根据某物实现出”
应用域,复合意味着has-a;
实现域,复合意味着is-implemented-in-terms-of;
条款39: 明智而审慎的使用private继承?
如果类之间的继承关系是private,编译器不会将一个继承类对象转换为一个基类对象,这意味着private继承并不是is-a的关系,意味着
是is-implemented-in-terms-of。
1. class Person 2. { 3. protected: 4. string name; 5. }; 6. 7. class Student:private Person 8. { 9. private: 10. string schoolNumber; 11. }; 12. 13. void eat(const Person& p) 14. { 15. cout<<"eat"< Private继承好处,即base class 大小为空时,调用private继承可以使派生类占据的空间大小最小化 条款40:明智审慎使用多重继承 多重继承容易导致歧义; 两重拷贝; 非必要不要使用virtual bases,virtual 继承造成class体积过大; "public继承某个Interface class"和"private继承某个协助实现的class"的两点相组合 1. class IPerson { // this class specifies the 2. public: // interface to be implemented 3. virtual ~IPerson(); 4. virtual std::string name() const = 0; 5. virtual std::string birthDate() const = 0; 6. }; 7. class DatabaseID { ... }; // used below; details are 8. // unimportant 9. class PersonInfo { // this class has functions 10. public: // useful in implementing 11. explicit PersonInfo(DatabaseID pid); // the IPerson interface 12. virtual ~PersonInfo(); 13. virtual const char * theName() const; 14. virtual const char * theBirthDate() const; 15. virtual const char * valueDelimOpen() const; 16. virtual const char * valueDelimClose() const; 17. ... 18. }; 19. class CPerson: public IPerson, private PersonInfo { // note use of MI 20. public: 21. explicit CPerson( DatabaseID pid): PersonInfo(pid) {} 22. virtual std::string name() const // implementations 23. { return PersonInfo::theName(); } // of the required 24. // IPerson member 25. virtual std::string birthDate() const // functions 26. { return PersonInfo::theBirthDate(); } 27. private: // redefinitions of 28. const char * valueDelimOpen() const { return ""; } // inherited virtual 29. const char * valueDelimClose() const { return ""; } // delimiter 30. }; // functions 7模板与泛型编程 条款41:隐式接口和编译期多态 编译期多态和运行期多态 平常的多态就是运行期多态;模板就是编译期多态,函数直到编译的时候才根据对象来确定 显示接口和隐式接口 void DoSometing(T& w) { if(w.size()>10&&w!=someNastWidge) { T temp(w); temp.normalize(); temp.swap(w); } } 显示接口(explicit interface): 源代码可以找到这个 interface(接口)(例如,Widget 的 .h 文件)以看清楚它是什么样子的,可以找到w对象的size()源代码实现,所以我们称其为一个 explicit interface(显式接口)——它在源代码中显式可见。 隐式接口:用模板实现,找不到源代码实现,因为size()是编译时确定的。 template 条款42:了解typename的双重意义 用模板时,class和typename基本没有不同 1. template 以上代码似乎没问题,但最好在const_iterator前加上typename Typename C::const_iterator iter(container.begin()); 告诉c++ const_iterator是个类型,不是变量; Typename标识套嵌从属类型名称 条款43:学习处理模板化基类的名称 基类模板可能被特化,成员可能发生改变,所以c++禁止子类模板直接寻找基类模板的成员。 解决办法:this指针、using 基类模板的成员、明确指出作用域(修饰) 条款44:将与参数无关的代码抽离templates 使用template可能会导致代码膨胀 SquareMatrix 这里生成了两份代码 条款45:运用成员模板兼容所有类型 template 条款46:需要类型转化时请为模板定义非成员函数 定义函数模板后,在使用该模板时,不要出现隐式转化的情况,因为此时函数模板还没有具现化出来;那怎么解决呢?我们可以把该函数模板声明为一个类的成员函数,这样存在隐式转化时,函数已经被具现化了。最好把函数的定义inline到类中。 8 定制new和delete 条款49:了解new-handler的行为 当new操作分配内存失败时,返回null指针,同时跑出异常,通过new-handler处理。 new_handler set_new_handler(new_handler p) 条款50:了解new-handler的行为 条款51:了解new-handler的行为 条款52:了解new-handler的行为