Effective C++总结

文章目录

Effective C++

一.让自己习惯C++
条款1.视C++为一个联邦

C++以C语言为基础,添加了C With Object, Template C++和STL

条款02.尽量以const,enum,inline替换#define

1.const 与#define的区别:
① 类型和安全检查不同:const定义的常数是变量,带类型,#define定义的只是个文本,不带类型
② 编译器处理方式不同:#define在预处理阶段进行处理,const常量在编译运行时处理。
③ 存储方式不同:
   #define只是展开,宏定义是不分配内存,在变量定义时分配内存。
  const常量在内存在内存中分配。

条款03.尽可能使用const
条款04.确定对象使用前被初始化

  1.在构造函数中,只有初始化列表才能算是初始化,在构造函数内部执行的是变量的赋值操作,已经不算是初始化操作。非静态const成员变量和引用成员变量必须使用成员初始化列表初始化。
① 最好是在初始化列表中进行初始化操作
② 内置类型要手动初始化,不同平台不能保证对内置类型进行初始化
  2. C++对“定义于不同编译单元内(不同.cpp和.h文件)的non-local static对象”的初始化次序没有明确的定义。
  解决方案:需要将每个non-local static对象搬到自己专属函数内(该对象在此函数内被声明为static),这个函数返回一个reference指向它所含的对象。用户调用这些函数,不直接涉及这些对象(单例模式)。
  3. 单例模式:
  ① 一个类只有(只能)创建一个实例。
  ② 这个实例必须自行创建(系统初始化时/用户调用时创建),不能通过用户创建。
  ③ 提供该实例的一个全局访问点

二.构造/析构/赋值运算
条款05.C++默认编写并调用函数

   编译器默认实现的函数:默认构造函数,析构函数,拷贝构造函数,赋值构造函数。若没有声明或定义这些函数,编译器会默认自动生成。若成员变量中包含引用变量,则应当禁止copy assignment操作符。

条款06. 不想使用默认生成的函数,可以明确拒绝  

   若默认构造函数和赋值构造函数不想被外面调用可以将其声明为private,且只声明而不定义。

条款07. 多态基类声明virtual析构函数

  当base类指针指向derived类时(即多态的用法),在释放对象时,如果base类的析构函数不是virtual,那么编译器将这个指针视为base类类型,只会释放掉base类的空间,导致derived类的内存泄露。如果base类的析构函数是virtual,在释放内存时会将derived类和base类的内存空间全部释放掉,防止内存的泄露。
如果一个class不用做base类,其析构函数不用设置为virtual,否则由于virtual函数的虚函数表机制而占用更大的内存。

条款08.别让异常逃离析构函数
条款09.绝不在构造和析构过程中调用virtual函数

  由于父类的构造函数发生在子类之前,而此时子类的成员变量等并未初始化,因此在父类的构造函数中调用virtual函数,绝对不会调用子类的方法,即使现在你在创建一个子类对象。换句话说,在构造函数中调用的virtual函数,都会下降到父类类型,即都不是virtual函数。同样的道理,子类析构函数调用在父类之前,因此在父类析构函数调用virtual函数时,子类都不存在了,你让编译器怎么调用。因此一定不要再构造和析构中调用virtual函数。
【base类与derived类构造和析构函数的调用顺序:】
① 首先调用base类构造函数,然后调用derived类构造函数。
② base类构造函数调用时会初始化base类成员以及derived类继承的base类成员 ==> derived类构造函数调用时只会初始化derived类中增加的成员。
③ 当base类构造函数初始化derived类中继承的base类函数时,对象仍为基类对象。

条款10. 令operator=返回reference to *this

   为了提高效率,且保证赋值运算符能够进行连续运算。

条款11. 在operator=中处理“自我赋值”
条款12. 复制对象的时候勿忘其每个部分

  derived类的copy构造函数既要本类的成员,也要复制base类成员。复制base类成员时,调用base类的copy构造函数。

三.资源管理
条款13. 以对象管理资源

   1.利用工厂模式来管理资源。
   2. 两个常用的自动管理资源的类是shared_ptr和auto_ptr。shared_ptr和auto_ptr只能释放单个内存,不能释放多个内存。
  ① auto_ptr的复制动作(copy构造函数和copy assignment运算符)导致对象被清空,后续不能再对auto_ptr对象进行任何操作。
   ② shared_ptr使用引用计数的原理实现对象共享的目的,并且在计数为0时自动释放对象

条款14.在资源管理类中小心copy行为
条款15.在资源管理类中提供对原始资源的访问
条款16.成对的使用new和delete

   使用new申请内存,必须使用delete释放内存,使用new [] 申请内存,必须使用delete []释放内存。

条款17.以单独的语句将newed对象置入shared_ptr

  不要写下类似Function(shared_ptr(new Class), x()),其中Function和x为函数,Class是一个类。原因在于shared_ptr的构造分为两步:第一步是new Class创建一个对象,第二步是执行构造函数,但是像上面那样写可能导致x()在两步之间运行(与编译器如何编译代码有关了),如果这个时候x()发生了异常,那么就会导致内存泄漏。因此初始化shared_ptr的时候,最好不要掺杂其他东西。通常将上述语句分为两个步骤,先创建对象,然后再执行构造函数。

条款18.让接口容易被正确使用,不易被误用

  1.促进正确使用:保证接口的一致性,以及内置类型的兼容
  2.防止误用:消除客户资源管理,限制类型上的操作。为了防止程序接口被错误使用,应当使无效的代码不通过编译,而不是在程序运行期来检测无效代码的错误。

条款19.设计class如设计type
条款20.以pass-by-reference-const替换pass-by-value

   1.效率问题:pass-by-value会多次调用构造函数和析构函数,同时会产生临时对象,效率更低。
  2. 对象切割问题:当以pass-by-value方式传递参数,若函数参数是base类,则无论实参是base类对象还是derived类对象,则在函数内部都表型为base类对象。如果对象属于内置类型,pass-by-value比pass-by-reference的效率要高。

条款21.必须返回对象时,不要返回其reference

  ①不要返回临时对象的引用,临时对象在函数结束时会被释放内存。
  ②不要返回堆上分配对象的引用,这违背了new和delete成对出现的原则
  ③不要返回static对象引用,static为静态变量,存放在全局区。由于内存共享,多次调用会出现问题。

条款22.将成员变量声明为private

  保证类的封装性,利用成员函数来控制成员变量的读写/控制。

条款23.宁以non-member、non-friend函数替换member函数

  类的封装性不是将所有函数都写在类中,而是根据能够改变事物,且只影响到有限客户。

条款24.若所有参数皆需类型转换,那么请采用non-member函数

  文中举了一个有理数Rational运算的例子,在类中加入一个operator*(const Rational& other)的函数,可以实现类似 rational * 2的操作,其中2是个int,但是因为rational有一个以int为参数的构造,因此编译器帮你执行了隐式类型转换。但是反过来写2 * rational的时候,编译就报错了。因为2是个int,并没有operator这个函数。但是为什么这样写就没有执行隐式类型转换呢?这又引出一个问题:隐士类型转换的合格条件是什么?答案是:必须是参数列中的参数才是隐士类型转换的有效参与者,类的执行者也就是’.'前面的那个对象(this指向的对象,比如说rational.func()中的rational是类执行者,相当于他是函数的调用人,地位较高,不能参与隐式类型转换),这就解释了为什么2放在前面是不行的。解决此种问题的方法是提供一个non-member的operator(Rational a, Rational b)即可。

条款25. 考虑写出一个不抛出异常的swap函数

  swap是STL中一个函数,是异常安全性编程的关键函数,swap(a,b);可以交换a,b两个对象,但原始的(STL)swap函数通过Copy构造函数和copy assignment实现,涉及三个对象的复制,当类中有一个或多个存放大量数据的数据类时效率低,而类的交换本质是数据的交换,为了提高效率,应当只复制数据类。

namespace std{
		template<typename T>
		void swap(T &a,T &b){
			T temp(a);
			a=b;
			b=temp;
		}	
}

  因此,对swap函数的使用分为四种情况:
  🕐 直接使用swap函数(STL)
  🕑 提供一个public swap函数,函数的参数为数据类,在函数中调用STL中的swap函数

using std::swap;
swap(pIml,Other.piml);

  🕒 若class有template,则在template中提供一个non-member swap,并调用STL中的swap函数
  🕔 若代码中仅有一个class,特化(将std中的原函数,指定一个class可以使用)STL中的swap函数

template<>
void swap<widget><widget &a,widget & b){
		swap(a.data,b.data);  //data为widget类的数据类
}
四.实现
条款26. 尽可能延后变量定义的出现时间

  🕐 函数中异常会导致定义的变量没有被使用。

void test(){
	string data;
	.... //其他函数(程序),若在此出现异常,则data变量定义却未被使用
}

  🕑 变量延后定义,使变量有确切的初值时,通过copy构造函数初始化,来提高效率。(default构造函数比copy构造函数初始化更耗时间)

1. 	string data;
		data=passward;         //not good
2. 	string data(passward);   //good
条款27. 尽量少做转型动作
条款28. 避免返回handles指向对象内部成分(handles->reference,指针,迭代器)

  对象内部部分既包含public部分也包含private部分。若函数返回handles可能会导致类的封装性变差。
  🕐 如果const成员函数返回一个handles,而该handles的返回类型在本类之外,则该成员函数的调用者可以修改成员函数中的数据。   解决方法是:将该成员函数的返回值设为const
  🕑handles返回的值所指的东西不存在。

class Point{
		public:
			void setX(int newval);
}
struct	RectData{
		Point ulhc;
}
class Rectangle{
		public:
				Point & upperLeft() const {return pData->ulhc;}
				const Point & upperSenondLeft() const {return pData->ulhc;}
		private:
			     std::trl::shared_ptr<RectData>	pData;
}
const Rectangle boundingbox(const GUIObject &obj){
	...
}
class GUIObject {..... }
int main(){
	Point coord1(0,0);
	Point coord2(100,100);
	const Rectangle rec(coord1,coord2);         
	rec.upperLeft().seX(50);   //rec对象的本意是不改变其成员变量的值,但是函数upperLeft()返回的是引用,
														//且不是const,这使返回的对象可以修改成员变量
	GUIObject *pgo;
	const Point* UpperLeft=&(boundingBox(*pgo).upperSenondLeft();)         //由于Rectangle是by value返回值,boundingBox(*pgo)在调用结束后返回一个临时变量,
																	//在调用upperSenondLeft()后,这个临时变量被销毁,则该Point指针所指的东西不存在。
	
}
条款29. 为"异常安全"而努力是值得的

  异常安全分为两个步骤:一是不泄漏资源,二是不允许数据的破坏
  1. 不泄漏资源:通常利用RAII技术(条款13),利用智能指针shared_ptr或者利用类来管理资源。
  2. 不允许数据的破坏:通常利用copy and swap技术:在打算修改的对象复制一个副本,在副本上进行修改,等修改完成,且没有异常抛出时,将副本和原对象进行置换swap

例如:有个class 来表示带背景图片的GUI菜单类:
struct	PMImpl{     //创建一个struct来存储背景图片数据
		std::trl::shared_ptr<Image> bgImage;
		int imageChange;
}
class Lock{     //利用Lock类来管理互斥锁资源
		public:
				explicit Lock(Mutex *pm):mutexPtr(pm){
						lock(mutexPtr);
				}
				~Lock(){
						unlock(mutexPtr);
				}
		private:
				Mutex  mutexPtr;
};
class PrettyMenu{
		public:
				void PrettyMenu::changeBackground(std::istream &imgSrc)
		.....
		private:
				Mutex mutex;
				std::trl::shared_ptr<PMImpl> pimpl;
};
void PrettyMenu::changeBackground(std::istream &imgSrc){
		using std::swap;
		Lock m1(&mutex);		//RAII技术
		std::trl::shared_ptr<PMImpl> pNew(new PMImpl(* pimpl)) ;   //将pimpl复制,创建一个pNew副本
		pNew->bgImage.reset(new Image(imgSrc))   //修改副本,用新实例化的imgSrc修改复制后的pNew副本
		++pNew->imageChange;
		swap(pimpl,pNew);     //交换pNew与pimpl		
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值