`
jandroid
  • 浏览: 1891642 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

[转]C++异常处理 4

 
阅读更多

}

很多window系统有C-like接口,使用象like createWindow 和 destroyWindow函数来获取和释放window资源。如果在w对应的window中显示信息时,一个异常被抛出,w所对应的window将被丢失,就象其它动态分配的资源一样。

解决方法与前面所述的一样,建立一个类,让它的构造函数与析构函数来获取和释放资源:

file://一个类,获取和释放一个window 句柄

class WindowHandle {

public:

  WindowHandle(WINDOW_HANDLE handle): w(handle) {}

 ~WindowHandle() { destroyWindow(w); }

  operator WINDOW_HANDLE() { return w; }    // see below

private:

 WINDOW_HANDLE w;

 // 下面的函数被声明为私有,防止建立多个WINDOW_HANDLE拷贝

 file://有关一个更灵活的方法的讨论请参见下面的灵巧指针

 WindowHandle(const WindowHandle&);

 WindowHandle& operator=(const WindowHandle&);

};

这看上去有些象auto_ptr,只是赋值操作与拷贝构造被显式地禁止(参见More effective C++条款27),有一个隐含的转换操作能把WindowHandle转换为WINDOW_HANDLE。这个能力对于使用WindowHandle对象非常重要,因为这意味着你能在任何地方象使用raw WINDOW_HANDLE一样来使用WindowHandle。(参见More effective C++条款5 ,了解为什么你应该谨慎使用隐式类型转换操作)


通过给出的WindowHandle类,我们能够重写displayInfo函数,如下所示:

// 如果一个异常被抛出,这个函数能避免资源泄漏

void displayInfo(const Information& info)

{

 WindowHandle w(createWindow());

 在w对应的window中显式信息;

}

即使一个异常在displayInfo内被抛出,被createWindow 建立的window也能被释放。

资源应该被封装在一个对象里,遵循这个规则,你通常就能避免在存在异常环境里发生资源泄漏。但是如果你正在分配资源时一个异常被抛出,会发生什么情况呢?例如当你正处于resource-acquiring类的构造函数中。还有如果这样的资源正在被释放时,一个异常被抛出,又会发生什么情况呢?构造函数和析构函数需要特殊的技术。你能在More effective C++条款10和More effective C++条款11中获取有关的知识。

抛出一个异常的行为
个人认为接下来的这部分其实说的很经典,对我们理解异常行为/异常拷贝是很有帮助的。

条款12:理解“抛出一个异常”与“传递一个参数”或“调用一个虚函数”间的差异

从语法上看,在函数里声明参数与在catch子句中声明参数几乎没有什么差别:

class Widget { ... }; file://一个类,具体是什么类
// 在这里并不重要
void f1(Widget w); // 一些函数,其参数分别为
void f2(Widget& w); // Widget, Widget&,或
void f3(const Widget& w); // Widget* 类型
void f4(Widget *pw);
void f5(const Widget *pw);
catch (Widget w) ... file://一些catch 子句,用来
catch (Widget& w) ... file://捕获异常,异常的类型为
catch (const Widget& w) ... // Widget, Widget&, 或
catch (Widget *pw) ... // Widget*
catch (const Widget *pw) ...

你因此可能会认为用throw抛出一个异常到catch子句中与通过函数调用传递一个参数两者基本相同。这里面确有一些相同点,但是他们也存在着巨大的差异。

让我们先从相同点谈起。你传递函数参数与异常的途径可以是传值、传递引用或传递指针,这是相同的。但是当你传递参数和异常时,系统所要完成的操作过程则是完全不同的。产生这个差异的原因是:你调用函数时,程序的控制权最终还会返回到函数的调用处,但是当你抛出一个异常时,控制权永远不会回到抛出异常的地方。

有这样一个函数,参数类型是Widget,并抛出一个Widget类型的异常:

// 一个函数,从流中读值到Widget中
istream operator>>(istream& s, Widget& w);
void passAndThrowWidget()
{
Widget localWidget;
cin >> localWidget; file://传递localWidget到 operator>>
throw localWidget; // 抛出localWidget异常
}
当传递localWidget到函数operator>>里,不用进行拷贝操作,而是把operator>>内的引用类型变量w指向localWidget,任何对w的操作实际上都施加到localWidget上。这与抛出localWidget异常有很大不同。不论通过传值捕获异常还是通过引用捕获(不能通过指针捕获这个异常,因为类型不匹配)都将进行lcalWidget的拷贝操作,也就说传递到catch子句中的是localWidget的拷贝。必须这么做,因为当localWidget离开了生存空间后,其析构函数将被调用。如果把localWidget本身(而不是它的拷贝)传递给catch子句,这个子句接收到的只是一个被析构了的Widget,一个Widget的“尸体”。这是无法使用的。因此C++规范要求被做为异常抛出的对象必须被复制。

即使被抛出的对象不会被释放,也会进行拷贝操作。例如如果passAndThrowWidget函数声明localWidget为静态变量(static),

void passAndThrowWidget()
{
static Widget localWidget; // 现在是静态变量(static);
file://一直存在至程序结束

cin >> localWidget; // 象以前那样运行
throw localWidget; // 仍将对localWidget
} file://进行拷贝操作
当抛出异常时仍将复制出localWidget的一个拷贝。这表示即使通过引用来捕获异常,也不能在catch块中修改localWidget;仅仅能修改localWidget的拷贝。对异常对象进行强制复制拷贝,这个限制有助于我们理解参数传递与抛出异常的第二个差异:抛出异常运行速度比参数传递要慢。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics