Effective C++读书笔记8

条款28:避免返回handles指向对象内部成分

class Point{
	
public:
	Point(int x, int y);
	//...
	void setX(int newVal);
	void setY(int newVal);
	//...
};

struct RectData{
	Point ulhc;
	Point lrhc;
};

class Rectangle{
	//...
private:
	std::shared_ptr<RectData> pData;
};

class Rectangle{
public:
	//...
	Point& upperLeft() const { return pData->ulhc;}
	Point& lowerRight() const {return pData->lrhc;}
};

这样的设计可通过编译,但是是错误的,也是自相矛盾的。两个函数都被声明为const,说明不会去修改Rectangle。另一方面两个函数却都返回reference指向private数据,调用者于是可以通过reference更改内部数据!

这给我们的教训是:第一,成员变量的封装性最多只等于返回其reference的函数的访问级别。本例中ulhc和lrhc被声明为private,它们实际上却是public的。

第二,如果const成员函数传出一个reference,后者所指对象与自身有关联,而他又被存储于对象之外,那么这个函数的调用者可以修改那笔数据。

我们在这些函数身上遭遇的两个问题可以轻松去除,只要对他们的返回类型加上const即可。但即使如此,upperleft和lowerRight还是返回了“代表对象内部”的handles,这可能会导致dangling handles:即这种handles所指的对象不复存在。这种不复存在的对象最常见的来源就是函数的返回值,例如:

class GUIObject{};
const Rectangle boundingBox(const GUIObject& obj);

GUIObject* pgo;
const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft());

对boundingBox的调用获得一个新的、暂时的Rectangle对象。是个临时对象,随后upperleft作用于临时对象身上,返回一个reference只想temp的一个内部成分。于是pUpperLeft指向那个Point对象。但是那个语句结束之后,boundingBox的返回值,也就是我们所说的临时对象将被销毁,最终导致pUpperLeft指向一个不再存在的对象。

但不是说你绝对不可以让成员函数返回handle,有时候你必须那么做。例如operator[],就允许你采摘string和vector的个别元素,这些数据会随着容器被销毁而销毁。但这样的函数毕竟是例外,不是常态。

请记住:

避免返回handles,包括reference,指针,迭代器,指向对象内部。遵守这个条款可增加封装性,帮助const成员函数的行为像个const,并将发生dangling handle的可能性将至最低。

条宽29:为异常安全而努力是值得的

(妈的,我写了两遍,放到草稿里,都没保存住,什么他妈玩意!!!!!!!)


条款30:透彻了解inlining的里里外外

inline函数背后的整体概念是:将对此函数的每一个调用都以函数本体替换之。这会增加目标代码的大小。在一台内存有限的机器上,过度热衷inline会造成程序体积太大,即使拥有虚拟内存,inline造成的代码膨胀亦会导致额外的换页行为,降低指令高速缓存装置的击中率

记住,inline只是对编译器的一个申请,不是强制命令,这项申请可以隐喻提出,也可以明确提出。隐喻方式是将函数定义于class定义内:

class Person{
public:
	int age const{return theAge;}  //一个隐喻inline申请:age函数被定义于class定义式内。
private:
	int theAge;
};

明确生命inline函数的做法是在其定义式前加上inline关键字,标准的max template(来自algorithm)往往这样实现:
template<typename T>
inline constT& std::max(const T& a, const T& b)
{return a < b ? b: a}

大多数编译器拒绝将太过复杂(例如带有循环或者递归)的函数inline,而所有对virtual函数的调用也都会是inline落空,因为virtual意味着:等待,直到运行期才确定调用哪个函数,而inline意味着:执行前,先将调用动作替换为被调用函数的本体。

一个表面上看似inline的函数是否真是inline,取决于你的建制环境,主要取决于编译器。大多数编译器会:如果它们无法将你要求的函数inline化,会给你一个警告信息(条款53).

有时候虽然编译器有意愿inline某个函数,但是可能为该函数生成一个函数本体,例如:如果程序要取某个inline函数的地址,编译器通常必须为此函数生成一个outlined函数本体,毕竟编译器哪有能力提出一个指针指向并不存在的函数呢?

实际上构造函数和析构函数是inline的糟糕候选人:

class Base{
public:
private:
	std::string bm1, bm2;
};
class Derived: public base{
public:
	Derived(){}   //Derived构造函数是空的,是吗?
private:
	std::string dm1, dm2, dm3;
};

C++对于对象被创建和被销毁时发生什么事做了各式各样的保证。当你使用new,动态创建的对象被其构造函数自动初始化;当你使用delete,对应的析构函数会被调用。当你创建一个对象,其每个base class及每一个成员变量都会被自动构造;当你销毁一个对象,反向程序的析构行为亦会自动发生。如果有个异常在对象构造期间被抛出,该对象已构造好的那一部分会被自动销毁。所以编译器为上述的那个表面上看起来为空的Derived构造函数所产生的代码,相当于:
Derived::Derived(){
	Base::Base();
	try{dm1.std::string::string();}
	catch(...){
		Base::~Base();
		throw;
	}
	
	try{dm2.std::string::string();}
	catch(...){
		dm1.std::string::~string();
		Base::~Base();
		throw;
	}
	
	try{dm1.std::string::string();}
	catch(...){                                   //如果抛出异常
		dm2.std::string::~string();           //销毁dm2和dm1
		dm1.std::string::~string();
		Base::~Base();
		throw;
	}
	
}
这个就能精确反映Derived的空白构造函数必须提供的行为。Derived构造函数至少一定会陆续调用其成员变量和base class两者的构造函数,而那些调用会 影响编译器是否对此空白函数inline。

相同的理由也适用于base构造函数,所以如果它被inline,所有替换base构造函数调用而插入的代码也都会被插入到derived构造函数调用内。

程序库设计者必须评估将函数生命为inline的冲击:inline函数无法随着程序的升级而升级。换句话说如果f是程序库内的一个inline函数,客户将f函数本体编译进其程序中,一旦程序设计者决定改变f,所有用到f的客户端程序都必须重新编译。然而如果f是noninline函数,一旦它有任何修改,客户端只需要重新连接就好。


请记住:

1.将大多数inline限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。

2.不要只因为function template出现在头文件,就将它们声明为inline。


条款31:将文件间的编译依存关系将至最低(没怎么看懂)

请记住:

1.支持编译依存性最小化的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是handle classes和Interface classes。

2.程序库头文件应该以完全且仅有声明式的形式存在这种做法不论是否涉及templates都使用。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值