Effective C++ 条款28、29

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

避免返回handles(包括references、指针、迭代器)指向对象内部。遵守这个条款可增加封装性,帮助const成员函数的行为像个const,并将发生“虚吊号码牌(dangling handles)”的可能性降至最低。

class Point {
public:
	Point(int x, int y);

	void setX(int newVal);
	void setY(int newVal);
}
struct RectData {
	Point ulhc;//ulhc="upper left-hand corner"(左上角)
	Point lrhs;//lrhc="lower right-hand corner"(右下角)
};
class Rectangle {
private:
	shared_ptr<RectData> pData;
public:
	Point& upperLeft()const { return pData -> ulhc; }
	Point& lowerRight()const { return pData->lrhc; }
};
Point coord1(0, 0);
Point coord2(100, 100);
const Rectangle rec(coord1, coord2);

rec.upperLeft().setX(50);//此处ulhc与lrhc都被声明为private,但实际上却是public,
                         //因为仍然是在直接修改私有成员。

造成上述的主要原因是成员函数返回references,与本条款相违背。对此进行改进如下,

class Rectangle {
private:
	shared_ptr<RectData> pData;
public:
	const Point& upperLeft()const { return pData->ulhc; }
	const Point& lowerRight()const { return pData->lrhc; }
};

上述代码还存在一个问题,upperLeft和lowerRight成员函数由于返回了“代表对象内部”的handles,有可能会造成dangling handles(空悬的号码牌):这种handles所指东西(的所属对象)不复存在。见如下错误代码的运用,

class GUIObject{};
const Rectangle boundingBox(const GUIObject& obj);
//客户使用这个函数:
GUIObject* pgo;
const Point& pUpperLeft = &(boundingBox(*pgo).upperLeft());

所以最好的办法就是不要返回handles,完美!

但也有例外,而且此时必须返回handles。例如operator[]就允许采摘string和vector的个别元素,而这些operator[]就是返回reference指向“容器内的数据”。

条款29 为“异常安全”而努力是值得的

异常安全函数提供以下三个保证:

i、基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下。没有任何对象或数据结构会因此而败坏,所有对象都处于一种内部前后一致的状态(例如所有的class约束条件都继续获得满足)。

ii、强烈保证:如果异常被抛出,程序状态不改变。调用这样的函数需有这样的认知:如果函数成功,就是完全成功,如果函数失败,程序会回复到“调用函数之前”的状态。

iii、不抛掷保证(nothrow):承诺绝不抛出异常,因为它们总是能够完成它们原先承诺的功能。(作用于内置类型(例如ints,指针等等)身上的所有操作都提供nothrow保证)这个nothrow是最难保证的,要求很高。


copy and swap策略(存在“连带影响效率”问题需要考虑):为打算修改的对象做出一份副本,然后在那副本身上做一切必要修改。若有任何修改动作抛出异常,原对象仍保持未改变状态。待所有改变都成功后,再将修改过的那个副本和原对象在一个不抛出异常的操作中置换。

struct PMImpl {
	shared_ptr<Image> baImage;
	int imageChanges;
};

class PrettyMenu {
	...
private:
	Mutex mutex;
	shared_ptr<PMImpl> pImpl;
};
void PrettyMenu::changeBackground(istream& imgSrc) {
	using std::swap;
	Lock ml(&mutex);//获得mutex的副本数据
	shared_ptr<PMImpl> pNew(new PMImpl(*pImpl));
	pNew->bgImage.reset(new Image(imgSrc));//修改副本   针对shared_ptr::reset说明,先生成新资源,
	++pNew->imageChanges;                  //再调用reset函数在其函数内部使用delete,删除旧资源,并指针指针向。

	swap(pImpl, pNew);
}

下面这一段话,说明编写代码是处于发展过程中,而不是一层不变的。

四十年前,满载goto的代码被视为一种美好实践,而今我们却致力写出结构化控制流。二十年前,全局数据被视为一种美好实践,而今我们却致力于数据的封装。十年前,撰写“未将异常考虑在内”的函数被视为一种美好实践,而今我们致力于写出“异常安全码”。----当然,此书出版于2011年。

以上内容均来自Scott Meyers大师所著Effective C++ version3,如有错误地方,欢迎指正!相互学习,促进!!

猜你喜欢

转载自blog.csdn.net/tt_love9527/article/details/80564846