条款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,如有错误地方,欢迎指正!相互学习,促进!!