Effective C++ 读书笔记之implemenations(2)
2008-12-10 18:57
357 查看
Item 28:Avoid returning "handles" to object internals(避免返回内部对象的句柄)
例如,有一个矩形类rectangle,需要表示它的四个顶点,可以定义以下类:
class point
{
public:
point(int x, int y);
...
void setX(int newVal);
void setY(int newVal);
...
};
class RectData
{
point ulhc; //upper left-hand corner
point lrhc; //lower right-hand corner
};
class Rectangle
{
...
private:
std::tr1::shared_ptr<RectData> pData;
};
有时候客户需要取得矩形的顶点,因此可以定义以下成员函数:
class Rectangle
{
...
private:
std::tr1::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
rec.upperLeft().setX(50); //not we expected.
上面讲的是引用(reference),对指针(pointer)以及迭代器(Vector)也是一样的,它们都是句柄(handles)。
禁止修改的办法是定义函数的返回类型为const,即:
class Rectangle
{
...
private:
std::tr1::shared_ptr<RectData> pData;
public:
const point& upperLeft() const {return pData->ulhc;}
const point& lowerRight() const {return pData->lrhc;}
};
尽管如此,上面的函数仍然返回指向内部对象的句柄,因此仍然会存在问题。特别的,可能会指向空的句柄。
例如:某个函数返回GUI对象的外框(bounding box):
class GUIObject {...};
const Rectangle boundingBox(const GUIObject& obj);
现在用户使用该函数:
GUIObject* pgo;
...
const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft()); //取得一个指针指向外框左上点
但是一旦pgo对象被销毁,则指向左上点的句柄不复存在,也即pUpperLeft成为空的指针(dangle handles空悬的指针)。这也就是为什么说返回一个指向内部对象的句柄总是危险的。但也并不是说绝对不能使用这种方式,有时候必须这样做。比如Operator []允许你返回strings或者vector的单个元素,那么这些数据会随着容器的销毁而被销毁。
Item 29:Strive for exception-safe code(为异常安全努力是值得的)
考虑一个实现GUI菜单的类,该类可以修改菜单的背景图片,另外该类被设计用于一个多线程的环境中,因此有一个互斥体mutex用于并发控制:
class PrettyMenu
{
public:
...
void changeBackground(std::istream& imgSrc); //change background image
...
private:
Mutex mutex; //mutex for this object
Image* bgImage; //Current background image
int imageChanges; //# of times image has been changed.
};
下面是changeBackground的实现:
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
lock(&mutex); //acquire mutex
delete bgImage; //get rid of old background
++imageChanges; //update image change count
bgImage = new Image(imgSrc); //install new background
unlock(&mutex); //release mutex
}
从exception-safe的角度,上面的代码不好。
exception-safe通常要满足两个要求:
不泄露资源。上面的代码中,如果new Image(imgSrc)失败了而产生异常,那么远unlock将永远不会执行,mutex永远不会被release;
不要让数据结构corrupted. 如果new Image(imgSrc)失败了而throw exception,那么bgImage将指向一个deleted object. 此外,imageChanges已经增加了。
对于第一个要求,比较好满足。在Item 13中讲到了利用资源管理的对象,Item 14中又介绍了Lock类来确保mutex可以被及时释放。
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
Lock m1(&mutex); //see Item 14
delete bgImage; //get rid of old background
++imageChanges; //update image change count
bgImage = new Image(imgSrc); //install new background
}
象Lock这样的资源管理类的最大一个好处就是使得函数代码短了,比如上面不再需要unlock了。
通常来说,短代码就是较好的代码,因为短不容易出错。
现在回来第二个问题,数据的完整性。首先,给出数据完整性的三种不同保证:
函数提供基本的保证(basic guarantee): 如果产生异常,程序的一切保证都在有效状态。但是有效状态有时是不可预测的,比如上面异常发生时,PrettyMenu可能继续显示原来的背景图片,也可能显示缺省的背景图片,用户对此是无法作出预测的。
函数提供很强的保证(Strong Guarantee):如果产生异常,程序的状态是不变的。换句话说,函数的操作是”原子“操作,要么全部成功,要么完全不成功。显然,这种方式容易控制,因此程序只有两种可选的状态;
函数绝不产生异常(nothrow guarantee):换句话说,函数总是能够完成它们原先承诺的功能。注意,也许我们会把程序抛出空的异常(empty exception)当成无异常(nothrow exception)。这是不对的,例如 int doSomthing() throw();这并不代表doSomthing不产生异常,而是说如果
异常安全代码必须抛出以上三种保证的一种。理想情况是最后一种,但是很难实现。因此通常会选择前两种。
对于上面的PrettyMenu类来说,要提供Strong Guarantee并不是太难,只需要将PrettyMenu's bgImage数据成员从built-in Image Pointer变为smart resource-managing pointers. 其次,将imageChanges的顺序改变一下。
class PrettyMenu
{
private:
...
std::tr1::shared_ptr <Image> bgImage; //Current background image
...
};
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
Lock m1(&mutex); //see Item 14
bgImage.reset(new Image(imgSrc)); //Replace bgImage's internal pointer with smart one.
++imageChanges; //update image change count
}
但是上面的实现还是有一个问题,即参数imageSrc。如果Image的构造器抛出一个异常,例如可能是读输入流的标记移除了,因此此时并不能提供强保证,只能提供基本保证。
对于这一点,一个通常的作法叫做"copy and swap",其思想是将打算修改的对象作一个附本,在副本上再做修改,一旦抛出异常,则原对象没有发生改变。如果修改成功,则再将副本和原对象对调即可。这种方法叫"pimpl idiom”,在Item 31 中有描述。
Struct PMImpl
{
std::tr1::shared_ptr<Image> bgImage;
int imageChanges;
};
class PrettyMenu
{
private:
Mutex mutex;
std::tr1::shared_ptr <PMImpl> pImpl;
};
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
Using std::swap; //See Item 25
Lock m1(&mutex); //see Item 14
std::tr1::shared_ptr <PMImpl> pNew(new PMImpl(*pImpl)); //copy obj. data
pNew->bgImage.reset(new Image(imgSrc)); //modify copy
++pNew->imageChanges;
swap(pImpl, pNew); //swap the new data into place
}
"copy and swap" 是对对象状态作出"全有或全无"改变的一个很好的办法,但是它没有办法提供强保证的异常安全,原因在于内嵌函数的“连带影响(Side effect)”。例如:下面函数包含两个子函数.
void SomeFunc()
{
... //对local状态作备份
f1();
f2();
... //将修改后的状态转换回来
};
很显然, 如果f1,f2的异常安全性比”强烈保证“低,则SomeFunc也难以实现”强烈保证“。此外,实现强烈保证另一个问题在于效率,copy-and swap的关键在于副本及交换,因此需要时间和空间上的消耗。
当”强烈保证“不切实际时,就必须提供基本保证。对许多函数来说,提供异常安全性之基本保证”是合理的,也是必须的。另外,如果系统中有一个函数不具备异常安全性,则整个系统也不具备安全性。在实际实现中,首先要调用“对象管理资源类”以阻止资源泄漏,然后挑选一个“异常安全性保证"来实施在你写的函数上。
例如,有一个矩形类rectangle,需要表示它的四个顶点,可以定义以下类:
class point
{
public:
point(int x, int y);
...
void setX(int newVal);
void setY(int newVal);
...
};
class RectData
{
point ulhc; //upper left-hand corner
point lrhc; //lower right-hand corner
};
class Rectangle
{
...
private:
std::tr1::shared_ptr<RectData> pData;
};
有时候客户需要取得矩形的顶点,因此可以定义以下成员函数:
class Rectangle
{
...
private:
std::tr1::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
rec.upperLeft().setX(50); //not we expected.
上面讲的是引用(reference),对指针(pointer)以及迭代器(Vector)也是一样的,它们都是句柄(handles)。
禁止修改的办法是定义函数的返回类型为const,即:
class Rectangle
{
...
private:
std::tr1::shared_ptr<RectData> pData;
public:
const point& upperLeft() const {return pData->ulhc;}
const point& lowerRight() const {return pData->lrhc;}
};
尽管如此,上面的函数仍然返回指向内部对象的句柄,因此仍然会存在问题。特别的,可能会指向空的句柄。
例如:某个函数返回GUI对象的外框(bounding box):
class GUIObject {...};
const Rectangle boundingBox(const GUIObject& obj);
现在用户使用该函数:
GUIObject* pgo;
...
const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft()); //取得一个指针指向外框左上点
但是一旦pgo对象被销毁,则指向左上点的句柄不复存在,也即pUpperLeft成为空的指针(dangle handles空悬的指针)。这也就是为什么说返回一个指向内部对象的句柄总是危险的。但也并不是说绝对不能使用这种方式,有时候必须这样做。比如Operator []允许你返回strings或者vector的单个元素,那么这些数据会随着容器的销毁而被销毁。
Item 29:Strive for exception-safe code(为异常安全努力是值得的)
考虑一个实现GUI菜单的类,该类可以修改菜单的背景图片,另外该类被设计用于一个多线程的环境中,因此有一个互斥体mutex用于并发控制:
class PrettyMenu
{
public:
...
void changeBackground(std::istream& imgSrc); //change background image
...
private:
Mutex mutex; //mutex for this object
Image* bgImage; //Current background image
int imageChanges; //# of times image has been changed.
};
下面是changeBackground的实现:
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
lock(&mutex); //acquire mutex
delete bgImage; //get rid of old background
++imageChanges; //update image change count
bgImage = new Image(imgSrc); //install new background
unlock(&mutex); //release mutex
}
从exception-safe的角度,上面的代码不好。
exception-safe通常要满足两个要求:
不泄露资源。上面的代码中,如果new Image(imgSrc)失败了而产生异常,那么远unlock将永远不会执行,mutex永远不会被release;
不要让数据结构corrupted. 如果new Image(imgSrc)失败了而throw exception,那么bgImage将指向一个deleted object. 此外,imageChanges已经增加了。
对于第一个要求,比较好满足。在Item 13中讲到了利用资源管理的对象,Item 14中又介绍了Lock类来确保mutex可以被及时释放。
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
Lock m1(&mutex); //see Item 14
delete bgImage; //get rid of old background
++imageChanges; //update image change count
bgImage = new Image(imgSrc); //install new background
}
象Lock这样的资源管理类的最大一个好处就是使得函数代码短了,比如上面不再需要unlock了。
通常来说,短代码就是较好的代码,因为短不容易出错。
现在回来第二个问题,数据的完整性。首先,给出数据完整性的三种不同保证:
函数提供基本的保证(basic guarantee): 如果产生异常,程序的一切保证都在有效状态。但是有效状态有时是不可预测的,比如上面异常发生时,PrettyMenu可能继续显示原来的背景图片,也可能显示缺省的背景图片,用户对此是无法作出预测的。
函数提供很强的保证(Strong Guarantee):如果产生异常,程序的状态是不变的。换句话说,函数的操作是”原子“操作,要么全部成功,要么完全不成功。显然,这种方式容易控制,因此程序只有两种可选的状态;
函数绝不产生异常(nothrow guarantee):换句话说,函数总是能够完成它们原先承诺的功能。注意,也许我们会把程序抛出空的异常(empty exception)当成无异常(nothrow exception)。这是不对的,例如 int doSomthing() throw();这并不代表doSomthing不产生异常,而是说如果
异常安全代码必须抛出以上三种保证的一种。理想情况是最后一种,但是很难实现。因此通常会选择前两种。
对于上面的PrettyMenu类来说,要提供Strong Guarantee并不是太难,只需要将PrettyMenu's bgImage数据成员从built-in Image Pointer变为smart resource-managing pointers. 其次,将imageChanges的顺序改变一下。
class PrettyMenu
{
private:
...
std::tr1::shared_ptr <Image> bgImage; //Current background image
...
};
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
Lock m1(&mutex); //see Item 14
bgImage.reset(new Image(imgSrc)); //Replace bgImage's internal pointer with smart one.
++imageChanges; //update image change count
}
但是上面的实现还是有一个问题,即参数imageSrc。如果Image的构造器抛出一个异常,例如可能是读输入流的标记移除了,因此此时并不能提供强保证,只能提供基本保证。
对于这一点,一个通常的作法叫做"copy and swap",其思想是将打算修改的对象作一个附本,在副本上再做修改,一旦抛出异常,则原对象没有发生改变。如果修改成功,则再将副本和原对象对调即可。这种方法叫"pimpl idiom”,在Item 31 中有描述。
Struct PMImpl
{
std::tr1::shared_ptr<Image> bgImage;
int imageChanges;
};
class PrettyMenu
{
private:
Mutex mutex;
std::tr1::shared_ptr <PMImpl> pImpl;
};
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
Using std::swap; //See Item 25
Lock m1(&mutex); //see Item 14
std::tr1::shared_ptr <PMImpl> pNew(new PMImpl(*pImpl)); //copy obj. data
pNew->bgImage.reset(new Image(imgSrc)); //modify copy
++pNew->imageChanges;
swap(pImpl, pNew); //swap the new data into place
}
"copy and swap" 是对对象状态作出"全有或全无"改变的一个很好的办法,但是它没有办法提供强保证的异常安全,原因在于内嵌函数的“连带影响(Side effect)”。例如:下面函数包含两个子函数.
void SomeFunc()
{
... //对local状态作备份
f1();
f2();
... //将修改后的状态转换回来
};
很显然, 如果f1,f2的异常安全性比”强烈保证“低,则SomeFunc也难以实现”强烈保证“。此外,实现强烈保证另一个问题在于效率,copy-and swap的关键在于副本及交换,因此需要时间和空间上的消耗。
当”强烈保证“不切实际时,就必须提供基本保证。对许多函数来说,提供异常安全性之基本保证”是合理的,也是必须的。另外,如果系统中有一个函数不具备异常安全性,则整个系统也不具备安全性。在实际实现中,首先要调用“对象管理资源类”以阻止资源泄漏,然后挑选一个“异常安全性保证"来实施在你写的函数上。
相关文章推荐
- Effective C++ 读书笔记之implemenations(3)
- Effective C++ 读书笔记之implemenations(1)
- Effective C++ 读书笔记(35-44):继承关系与面向对象设计
- 【C++】析构函数和virtual函数引发的隐晦问题 ——《Effective C++》读书笔记5
- 《Effective C++(第三版)》读书笔记
- Effective C++读书笔记之四:确定对象被使用前已先被初始化
- 《effective C++》读书笔记三——资源管理
- [C/C++]《Effective C++》读书笔记
- Effective C++ 读书笔记——尽量以const,enum,inline替换 #define
- Effective C++ 读书笔记之Part3.Resource Management
- Effective C++ 读书笔记二
- 函数继承Effective C++ 读书笔记之Part6.Inheritance and Object-Oriented Design
- Effective c++ 读书笔记
- Effective C++ 读书笔记(13)
- Effective C++ 读书笔记(14)
- Effective C++ 读书笔记(21)
- Effective C++ 读书笔记(24)
- Effective C++ 读书笔记(25)
- 《Effective C++》读书笔记
- Effective C++ 部分读书笔记