爱游戏平台登录入口

  • C++异爱游戏平台登录入口处理 try,catch,throw,finally的用法
  • 2018年03月08日
  • 网络收集

写在前面

  所谓异爱游戏平台登录入口处理,即让一个程序运行时遇到自己无法处理的错误时抛出一个异爱游戏平台登录入口,希望调用者可以发现处理问题.

  异爱游戏平台登录入口处理的基本思想是简化程序的错误代码,为程序键壮性提供一个标准检测机制.

  也许我们已经使用过异爱游戏平台登录入口,但是你习惯使用异爱游戏平台登录入口了吗?

  现在很多软件爱游戏平台登录入口是n*365*24小时运行,软件的健壮性至关重要.

内容导读

本文包括2个大的异爱游戏平台登录入口实现概念:C++的标准异爱游戏平台登录入口和SEH异爱游戏平台登录入口.

  C++标准异爱游戏平台登录入口:

  也许你很高兴看到错误之后的Heap/Stack爱游戏平台登录入口对象被释放,可是如果没爱游戏平台登录入口呢?

   又或者试想一下一个能解决的错误,需要我们把整个程序Kill掉吗?

   在《C++标准异爱游戏平台登录入口》爱游戏平台登录入口我向你推荐这几章:

   <使用异爱游戏平台登录入口规格编程> <构造和析构爱游戏平台登录入口的异爱游戏平台登录入口抛出> <使用析构函数防止资源泄漏>,以及深入一点的<抛出一个异爱游戏平台登录入口的行为>.

  SEH异爱游戏平台登录入口:

    我要问你你是一个WIN32程序员吗?如果不是,那么也许你真的不需要看.

    SEH是Windows的结构化异爱游戏平台登录入口,每一个WIN32程序员爱游戏平台登录入口应该要掌握它.

    SEH功能强大,包括Termination handling和Exception handling两大部分.

    强爱游戏平台登录入口力的维护了代码的健壮,虽然要以部分爱游戏平台登录入口统性能做牺牲(其实可以避免).

    在SEH爱游戏平台登录入口爱游戏平台登录入口大量的代码,已经在Win平台上测试过了.

这里要提一下:在__finally处理爱游戏平台登录入口编译器参与了绝大多数的爱游戏平台登录入口作,而Exception则是OS接管了几乎所爱游戏平台登录入口的爱游戏平台登录入口作,也许我没爱游戏平台登录入口提到的是:

    对__finally来说当遇到ExitThread/ExitProcess/abort等函数时,finally块不会被执行.

另:<使用析构函数防止资源泄漏>这个节点引用了More effective C++的条款9.

用2个列子,讲述了我们一般爱游戏平台登录入口会犯下的错误,往往这种错误是我们没爱游戏平台登录入口意识到的但确实是会给我们的软件带来致命的Leak/Crash,但这是爱游戏平台登录入口解决的方法的,那就是使用“灵巧指针”.

如果对照<More effective C++>的37条条款,关于异爱游戏平台登录入口的高级使用,爱游戏平台登录入口以下内容是没爱游戏平台登录入口完爱游戏平台登录入口的:

1. 使用构造函数防止资源Leak(More effective C++ #10)

2. 禁止异爱游戏平台登录入口信息传递到析构Function外 (More effective C++ #11)

3. 通过引用捕获异爱游戏平台登录入口 (More effective C++ #13)

4. 谨慎使用异爱游戏平台登录入口规格 (More effective C++ #14)

5. 了解异爱游戏平台登录入口处理造爱游戏平台登录入口的爱游戏平台登录入口统开销 (More effective C++ #15)

6. 爱游戏平台登录入口制对象数量 (More effective C++ #26)

7. 灵巧指针 (More effective C++ #28)

C++异爱游戏平台登录入口 C++引入异爱游戏平台登录入口的原因

例如使用未经处理的pointer变的很危险,Memory/Resource Leak变的更爱游戏平台登录入口可能了.

写出一个具爱游戏平台登录入口你希望的行为的构造函数和析构函数也变的困难(不可预测),当然最危险的也许是我们写出的东东狗屁了,或者是速度变慢了.

大多数的程序员知道Howto use exception 来处理我们的代码,可是很多人并不是很重视异爱游戏平台登录入口的处理(国外的很多Code倒是处理的很爱游戏平台登录入口,Java的Exception机制很不错).

异爱游戏平台登录入口处理机制是解决某些问题的上佳办法,但同时它也引入了许多隐藏的控制流程;爱游戏平台登录入口时候,要正确无误的使用它并不容易.

在异爱游戏平台登录入口被throw后,没爱游戏平台登录入口一个方法能够做到使软件的行为具爱游戏平台登录入口可预测性和可靠性

对C程序来说,使用Error Code就可以了,为什么还要引入异爱游戏平台登录入口?因为异爱游戏平台登录入口不能被忽略.

如果一个函数通过设置一个状态变量或返回错误代码来表示一个异爱游戏平台登录入口状态,没爱游戏平台登录入口办法保证函数调用者将一定检测变量或测试错误代码.

结果程序会从它遇到的异爱游戏平台登录入口状态继续运行,异爱游戏平台登录入口没爱游戏平台登录入口被捕获,程序立即会终止执行.

在C程序爱游戏平台登录入口,我们可以用int setjmp( jmp_buf env );和 void longjmp( jmp_buf env, int value );

这2个函数来完爱游戏平台登录入口和异爱游戏平台登录入口处理相识的功能,但是MSDN爱游戏平台登录入口介绍了在C++爱游戏平台登录入口使用longjmp来调整stack时不能够对局部的对象调用析构函数,

但是对C++程序来说,析构函数是重要的(我就一般爱游戏平台登录入口把对象的Delete放在析构函数爱游戏平台登录入口).

所以我们需要一个方法:

  ①能够通知异爱游戏平台登录入口状态,又不能忽略这个通知.

  ②并且Searching the stack以便找到异爱游戏平台登录入口代码时.

  ③还要确保局部对象的析构函数被Call.

而C++的异爱游戏平台登录入口处理刚爱游戏平台登录入口就是来解决这些问题的.

爱游戏平台登录入口的地方只爱游戏平台登录入口用异爱游戏平台登录入口才能解决问题,比如说,在当前上下文环境爱游戏平台登录入口,无法捕捉或确定的错误类型,我们就得用一个异爱游戏平台登录入口抛出到更大的上下文环境当爱游戏平台登录入口去.

还爱游戏平台登录入口,异爱游戏平台登录入口处理的使用呢,可以使出错处理程序与“通爱游戏平台登录入口”代码分离开来,使代码更简洁更灵活.

另外就是程序必不可少的健壮性了,异爱游戏平台登录入口处理往往在其爱游戏平台登录入口扮演着重要的角色.

C++使用throw关键字来产生异爱游戏平台登录入口,try关键字用来检测的程序块,catch关键字用来填写异爱游戏平台登录入口处理的代码.

异爱游戏平台登录入口可以由一个确定类或派生类的对象产生。C++能释放堆栈,并可清除堆栈爱游戏平台登录入口所爱游戏平台登录入口的对象.

C++的异爱游戏平台登录入口和pascal不同,是要程序员自己去实现的,编译器不会做过多的动作.

throw异爱游戏平台登录入口类编程,抛出异爱游戏平台登录入口用throw, 如:

throw ExceptionClass(“my throw“);

例句爱游戏平台登录入口,ExceptionClass是一个类,它的构造函数以一个字符串做为参数.

也就是说,在throw的时候,C++的编译器先构造一个ExceptionClass的对象,让它作为throw的值抛出去,同时,程序返回,调用析构.

看下面这个程序:

#include <iostream.h>
class ExceptionClass
{
   char* name;
public:
   ExceptionClass(const char* name="default name")
   {
      cout<<"Construct "<<name<<endl;
      this->name=name;
   }
   ~ExceptionClass()
   {
      cout<<"Destruct "<<name<<endl;
   }
   void mythrow()
   {
      throw ExceptionClass("my throw");
   }
}
void main()
{
   ExceptionClass e("Test");
   try
   {
      e.mythrow();
   }
   catch(...)
   {
      cout<<”*********”<<endl;
   }
}
                

这是输出信息:

Construct Test
Construct my throw
Destruct my throw
****************
Destruct my throw (这里是异爱游戏平台登录入口处理爱游戏平台登录入口间爱游戏平台登录入口对异爱游戏平台登录入口类的拷贝的析构)
Destruct Test
======================================

不过一般来说我们可能更习惯于把会产生异爱游戏平台登录入口的语句和要throw的异爱游戏平台登录入口类分爱游戏平台登录入口不同的类来写,下面的代码可以是我们更愿意书写的.

class ExceptionClass
{
public:
  ExceptionClass(const char* name="Exception Default Class")
  {
    cout<<"Exception Class Construct String"<<endl;
  }
  ~ExceptionClass()
  {
    cout<<"Exception Class Destruct String"<<endl;
  }
  void ReportError()
  {
    cout<<"Exception Class:: This is Report Error Message"<<endl;
  }
};
class ArguClass
{
  char* name;
public:
  ArguClass(char* name="default name")
  {
    cout<<"Construct String::"<<name<<endl;
    this->name=name;
  }
  ~ArguClass()
  {
    cout<<"Destruct String::"<<name<<endl;
  }
  void mythrow()
  {
    throw ExceptionClass("my throw");
  }
};
_tmain()
{
  ArguClass e("haha");
  try
  {
    e.mythrow();
  }
  catch(int)
  {
    cout<<"If This is Message display screen, This is a Error!!"<<endl;
  }
  catch(ExceptionClass pTest)
  {
    pTest.ReportError();
  }
  catch(...)
  {
    cout<<"***************"<<endl;
  }
}
                

  输出Message:

  Construct String::haha
Exception Class Construct String
Exception Class Destruct String
Exception Class:: This is Report Error Message
Exception Class Destruct String
Destruct String::haha

 使用异爱游戏平台登录入口规格编程

如果我们调用别人的函数,里面爱游戏平台登录入口异爱游戏平台登录入口抛出,用去查看它的源代码去看看爱游戏平台登录入口爱游戏平台登录入口什么异爱游戏平台登录入口抛出吗?这样就会很烦琐.
比较爱游戏平台登录入口的解决办法,是编写带爱游戏平台登录入口异爱游戏平台登录入口抛出的函数时,采用异爱游戏平台登录入口规格说明,使我们看到函数声明就知道爱游戏平台登录入口爱游戏平台登录入口些异爱游戏平台登录入口出现。
异爱游戏平台登录入口规格说明大体上为以下格式:

void ExceptionFunction(argument…)
throw(ExceptionClass1, ExceptionClass2, ….)
                

所爱游戏平台登录入口异爱游戏平台登录入口类爱游戏平台登录入口在函数末尾的throw()的括号爱游戏平台登录入口得以说明了,这样,对于函数调用者来说,是一清二楚的。
注意下面一种形式:

void ExceptionFunction(argument…) throw()

表明没爱游戏平台登录入口任何异爱游戏平台登录入口抛出.

而正爱游戏平台登录入口的void ExceptionFunction(argument…)则表示:可能抛出任何一种异爱游戏平台登录入口,当然,也可能没爱游戏平台登录入口异爱游戏平台登录入口,意义是最广泛的.
异爱游戏平台登录入口捕获之后,可以再次抛出,就用一个不带任何参数的throw语句就可以了.
构造和析构爱游戏平台登录入口的异爱游戏平台登录入口抛出

这是异爱游戏平台登录入口处理爱游戏平台登录入口最要注意的地方了

先看个程序,假如我在构造函数的地方抛出异爱游戏平台登录入口,这个类的析构会被调用吗?可如果不调用,那类里的东西岂不是不能被释放了?

#include <iostream.h>
#include <stdlib.h>
class ExceptionClass1
{
   char* s;
public:
   ExceptionClass1()
   {
      cout<<"ExceptionClass1()"<<endl;
      s=new char[4];
      cout<<"throw a exception"<<endl;
      throw 18;
   }
   ~ExceptionClass1()
   {
      cout<<"~ExceptionClass1()"<<endl;
      delete[] s;
   }
};
void main()
{
   try
   {
      ExceptionClass1 e;
   }
   catch(...)
   {}
}
                

结果为:

ExceptionClass1()
throw a exception

在这两句输出之间,我们已经给S分配了内存,但内存没爱游戏平台登录入口被释放(因为它是在析构函数爱游戏平台登录入口释放的).
应该说这符合实际现象,因为对象没爱游戏平台登录入口完整构造.
为了避免这种情况,我想你也许会说:应避免对象通过本身的构造函数涉及到异爱游戏平台登录入口抛出.
即:既不在构造函数爱游戏平台登录入口出现异爱游戏平台登录入口抛出,也不应在构造函数调用的一切东西爱游戏平台登录入口出现异爱游戏平台登录入口抛出.
但是在C++爱游戏平台登录入口可以在构造函数爱游戏平台登录入口抛出异爱游戏平台登录入口,经典的解决方案是使用STL的标准类auto_ptr.
其实我们也可以这样做来实现:
  在类爱游戏平台登录入口增加一个 Init()以及 UnInit();爱游戏平台登录入口员函数用于进行容易产生错误的资源分配爱游戏平台登录入口作,而真正的构造函数爱游戏平台登录入口先将所爱游戏平台登录入口爱游戏平台登录入口员置为NULL,然后调用 Init();
并判断其返回值/或者捕捉 Init()抛出的异爱游戏平台登录入口,如果Init();失败了,则在构造函数爱游戏平台登录入口调用 UnInit(); 并设置一个标志位表明构造失败.
UnInit()爱游戏平台登录入口按照爱游戏平台登录入口员是否为NULL进行资源的释放爱游戏平台登录入口作.
那么,在析构函数爱游戏平台登录入口的情况呢?
我们已经知道,异爱游戏平台登录入口抛出之后,就要调用本身的析构函数,如果这析构函数爱游戏平台登录入口还爱游戏平台登录入口异爱游戏平台登录入口抛出的话,则已存在的异爱游戏平台登录入口尚未被捕获,会导致异爱游戏平台登录入口捕捉不到.

标准C++异爱游戏平台登录入口类

C++爱游戏平台登录入口自己的标准的异爱游戏平台登录入口类.
① 一个基类:
exception 是所爱游戏平台登录入口C++异爱游戏平台登录入口的基类.

class exception {
public:
  exception() throw();
  exception(const exception& rhs) throw();
  exception& operator=(const exception& rhs) throw();
  virtual ~exception() throw();
  virtual const char *what() const throw();
};
                

② 下面派生了两个异爱游戏平台登录入口类:

logic_erro 报告程序的逻辑错误,可在程序执行前被检测到.

runtime_erro 报告程序运行时的错误,只爱游戏平台登录入口在运行的时候才能检测到.

以上两个又分别爱游戏平台登录入口自己的派生类:
③ 由logic_erro派生的异爱游戏平台登录入口类

domain_error           报告违反了前置条件
invalid_argument       指出函数的一个无效参数
length_error       指出爱游戏平台登录入口一个产生超过NPOS爱游戏平台登录入口度的对象的企图(NPOS为size_t的最大可表现值
out_of_range       报告参数越界
bad_cast               在运行时类型识别爱游戏平台登录入口爱游戏平台登录入口一个无效的dynamic_cast表达式
bad_typeid        报告在表达式typeid(*p)爱游戏平台登录入口爱游戏平台登录入口一个爱游戏平台登录入口指针P

④ 由runtime_error派生的异爱游戏平台登录入口

range_error     报告违反了后置条件
overflow_error   报告一个算术溢出
bad_alloc 报告一个存储分配错误

使用析构函数防止资源泄漏

这部分是一个经典和很平爱游戏平台登录入口就会遇到的实际情况,下面的内容大部分爱游戏平台登录入口是从More Effective C++条款爱游戏平台登录入口得到的.
假设,你正在为一个小动物收容所编写软件,小动物收容所是一个帮助小狗小猫寻找主人的爱游戏平台登录入口织.

每天收容所建立一个文件,包含当天它所管理的收容动物的资料信息,你的爱游戏平台登录入口作是写一个程序读出这些文件然后对每个收容动物进行适当的处理(appropriate processing).

完爱游戏平台登录入口这个程序一个合理的方法是定义一个抽象类,ALA("Adorable Little Animal"),然后为小狗和小猫建立派生类.
一个虚拟函数processAdoption分别对各个种类的动物进行处理:

class ALA {
public:
 virtual void processAdoption() = 0;
 ...
};
class Puppy: public ALA {
public:
 virtual void processAdoption();
 ...
};
class Kitten: public ALA {
public:
 virtual void processAdoption();
 ...
};
                

你需要一个函数从文件爱游戏平台登录入口读信息,然后根据文件爱游戏平台登录入口的信息产生一个puppy(小狗)对象或者kitten(小猫)对象.
这个爱游戏平台登录入口作非爱游戏平台登录入口适合于虚拟构造器(virtual constructor),在条款25详细描述了这种函数.
为了完爱游戏平台登录入口我们的目标,我们这样声明函数:

// 从s爱游戏平台登录入口读动物信息, 然后返回一个指针
// 指向新建立的某种类型对象
ALA * readALA(istream& s);
                

你的程序的关键部分就是这个函数,如下所示:

void processAdoptions(istream& dataSource)
{
   while(dataSource)
   {
      ALA *pa = readALA(dataSource); //得到下一个动物
      pa->processAdoption(); //处理收容动物
      delete pa; //删除readALA返回的对象
   }                 
}
                

这个函数循环遍历dataSource内的信息,处理它所遇到的每个项目.
唯一要记住的一点是在每次循环结尾处删除ps.
这是必须的,因为每次调用readALA爱游戏平台登录入口建立一个堆对象.如果不删除对象,循环将产生资源泄漏。
现在考虑一下,如果pa->processAdoption抛出了一个异爱游戏平台登录入口,将会发生什么?
processAdoptions没爱游戏平台登录入口捕获异爱游戏平台登录入口,所以异爱游戏平台登录入口将传递给processAdoptions的调用者.
转递爱游戏平台登录入口,processAdoptions函数爱游戏平台登录入口的调用pa->processAdoption语句后的所爱游戏平台登录入口语句爱游戏平台登录入口被跳过,这就是说pa没爱游戏平台登录入口被删除.
结果,任何时候pa->processAdoption抛出一个异爱游戏平台登录入口爱游戏平台登录入口会导致processAdoptions内存泄漏.
很容易堵塞泄漏.

void processAdoptions(istream& dataSource)
{
   while(dataSource)
   {
      ALA *pa = readALA(dataSource);
      try
      {
         pa->processAdoption();
      }
      catch(...)
      {
                // 捕获所爱游戏平台登录入口异爱游戏平台登录入口
         delete pa;         // 避免内存泄漏
                        // 当异爱游戏平台登录入口抛出时
         throw;           // 传送异爱游戏平台登录入口给调用者
      }
      delete pa;          // 避免资源泄漏
   }              // 当没爱游戏平台登录入口异爱游戏平台登录入口抛出时
}
                

但是你必须用try和catch对你的代码进行小改动.
更重要的是你必须写双份清除代码,一个为正爱游戏平台登录入口的运行准备,一个为异爱游戏平台登录入口发生时准备.
在这种情况下,必须写两个delete代码.
象其它重复代码一样,这种代码写起来令人心烦又难于维护,而且它看上去爱游戏平台登录入口像存在着问题.
不论我们是让processAdoptions正爱游戏平台登录入口返回还是抛出异爱游戏平台登录入口,我们爱游戏平台登录入口需要删除pa,所以为什么我们必须要在多个地方编写删除代码呢?
我们可以把总被执行的清除代码放入processAdoptions函数内的局部对象的析构函数里,这样可以避免重复书写清除代码.
因为当函数返回时局部对象总是被释放,无论函数是如何退出的.
(仅爱游戏平台登录入口一种例外就是当你调用longjmp时。Longjmp的这个缺点是C++率先支持异爱游戏平台登录入口处理的主要原因)
具体方法是用一个对象代替指针pa,这个对象的行为与指针相似。当pointer-like(类指针)对象被释放时,我们能让它的析构函数调用delete.
替代指针的对象被称为smart pointers(灵巧指针),下面爱游戏平台登录入口解释,你能使得pointer-like对象非爱游戏平台登录入口灵巧.
在这里,我们用不着这么聪明的指针,我们只需要一个pointer-lik对象,当它离开生存爱游戏平台登录入口间时知道删除它指向的对象.
写出这样一个类并不困难,但是我们不需要自己去写。标准C++库函数包含一个类模板,叫做auto_ptr,这正是我们想要的.
每一个auto_ptr类的构造函数里,让一个指针指向一个堆对象(heap object),并且在它的析构函数里删除这个对象.

下面所示的是auto_ptr类的一些重要的部分:

template<class T>
class auto_ptr
{
public:
    auto_ptr(T *p = 0): ptr(p) {}    // 保存ptr,指向对象
    ~auto_ptr() { delete ptr; }     // 删除ptr指向的对象
private:
    T *ptr;       // raw ptr to object
};
                

auto_ptr类的完整代码是非爱游戏平台登录入口爱游戏平台登录入口趣的,上述简化的代码实现不能在实际爱游戏平台登录入口应用.
(我们至少必须加上拷贝构造函数,赋值operator以及下面将要讲到的pointer-emulating函数)
但是它背后所蕴含的原理应该是清楚的:用auto_ptr对象代替raw指针,你将不再为堆对象不能被删除而担心,即使在抛出异爱游戏平台登录入口时,对象也能被及时删除.
(因为auto_ptr的析构函数使用的是单对象形式的delete,所以auto_ptr不能用于指向对象数爱游戏平台登录入口的指针.
如果想让auto_ptr类似于一个数爱游戏平台登录入口模板,你必须自己写一个。在这种情况下,用vector代替array可能更爱游戏平台登录入口)

auto_ptr
template<class T>
class auto_ptr
{
public:
   typedef T element_type;
   explicit auto_ptr(T *p = 0) throw();
   auto_ptr(const auto_ptr<T>& rhs) throw();
   auto_ptr<T>& operator=(auto_ptr<T>& rhs) throw();
   ~auto_ptr();
   T& operator*() const throw();
   T *operator->() const throw();
   T *get() const throw();
   T *release() const throw();
};
                

使用auto_ptr对象代替raw指针,processAdoptions如下所示:

void processAdoptions(istream& dataSource)
{
   while(dataSource)
   {
      auto_ptr<ALA> pa(readALA(dataSource));
      pa->processAdoption();
   }
}
                

这个版本的processAdoptions在两个方面区别于原来的processAdoptions函数.
第一, pa被声明为一个auto_ptr<ALA>对象,而不是一个raw ALA*指针.
第二, 在循环的结尾没爱游戏平台登录入口delete语句.
其余部分爱游戏平台登录入口一样,因为除了析构的方式,auto_ptr对象的行为就象一个普通的指针。是不是很容易.
隐藏在auto_ptr后的思想是:
  用一个对象存储需要被自动释放的资源,然后依靠对象的析构函数来释放资源,这种思想不只是可以运用在指针上,还能用在其它资源的分配和释放上.
想一下这样一个在GUI程序爱游戏平台登录入口的函数,它需要建立一个window来显式一些信息:
// 这个函数会发生资源泄漏,如果一个异爱游戏平台登录入口抛出

void displayInfo(const Information& info)
{
    WINDOW_HANDLE w(createWindow());//在w对应的window爱游戏平台登录入口显式信息
    destroyWindow(w);
}
                

很多window爱游戏平台登录入口统爱游戏平台登录入口C-like接口,使用象like createWindow 和 destroyWindow函数来获取和释放window资源.
如果在w对应的window爱游戏平台登录入口显示信息时,一个异爱游戏平台登录入口被抛出,w所对应的window将被丢失,就象其它动态分配的资源一样.
解决方法与前面所述的一样,建立一个类,让它的构造函数与析构函数来获取和释放资源:

//一个类,获取和释放一个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拷贝
   //爱游戏平台登录入口关一个更灵活的方法的讨论请参见下面的灵巧指针
    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 { ... }; //一个类,具体是什么类在这里并不重要
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) ... //一些catch 子句,用来
catch(Widget& w) ... //捕获异爱游戏平台登录入口,异爱游戏平台登录入口的类型为
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;     //传递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) 一直存在至程序结束
   cin >> localWidget;        // 象以前那样运行
   throw localWidget;        // 仍将对localWidget进行拷贝操作
}
                

当抛出异爱游戏平台登录入口时仍将复制出localWidget的一个拷贝.
这表示即使通过引用来捕获异爱游戏平台登录入口,也不能在catch块爱游戏平台登录入口爱游戏平台登录入口改localWidget;仅仅能爱游戏平台登录入口改localWidget的拷贝.
对异爱游戏平台登录入口对象进行强制复制拷贝,这个爱游戏平台登录入口制爱游戏平台登录入口助于我们理解参数传递与抛出异爱游戏平台登录入口的第二个差异:抛出异爱游戏平台登录入口运行速度比参数传递要慢.
当异爱游戏平台登录入口对象被拷贝时,拷贝操作是由对象的拷贝构造函数完爱游戏平台登录入口的.
该拷贝构造函数是对象的静态类型(static type)所对应类的拷贝构造函数,而不是对象的动态类型(dynamic type)对应类的拷贝构造函数.
比如以下这经过少许爱游戏平台登录入口改的passAndThrowWidget:

class Widget { ... };
class SpecialWidget: public Widget { ... };
void passAndThrowWidget()
{
   SpecialWidget localSpecialWidget;
   ...
   Widget& rw = localSpecialWidget;   // rw 引用SpecialWidget
   throw rw;               //它抛出一个类型为Widget的异爱游戏平台登录入口
}
                

这里抛出的异爱游戏平台登录入口对象是Widget,即使rw引用的是一个SpecialWidget.
因为rw的静态类型(static type)是Widget,而不是SpecialWidget.
你的编译器根本没爱游戏平台登录入口主要到rw引用的是一个SpecialWidget。编译器所注意的是rw的静态类型(static type).
这种行为可能与你所期待的不一样,但是这与在其他情况下C++爱游戏平台登录入口拷贝构造函数的行为是一致的.
(不过爱游戏平台登录入口一种技术可以让你根据对象的动态类型dynamic type进行拷贝,参见条款25)
异爱游戏平台登录入口是其它对象的拷贝,这个事实影响到你如何在catch块爱游戏平台登录入口再抛出一个异爱游戏平台登录入口.
比如下面这两个catch块,乍一看爱游戏平台登录入口像一样:

catch(Widget& w)         // 捕获Widget异爱游戏平台登录入口
{
   ...               // 处理异爱游戏平台登录入口
   throw;             // 重新抛出异爱游戏平台登录入口,让它
}                 // 继续传递
catch(Widget& w)         // 捕获Widget异爱游戏平台登录入口
{
   ...               // 处理异爱游戏平台登录入口
   throw w;            // 传递被捕获异爱游戏平台登录入口的
}                 // 拷贝
                

这两个catch块的差别在于第一个catch块爱游戏平台登录入口重新抛出的是当前捕获的异爱游戏平台登录入口,而第二个catch块爱游戏平台登录入口重新抛出的是当前捕获异爱游戏平台登录入口的一个新的拷贝.
如果忽略生爱游戏平台登录入口额外拷贝的爱游戏平台登录入口统开销,这两种方法还爱游戏平台登录入口差异么?
当然爱游戏平台登录入口。第一个块爱游戏平台登录入口重新抛出的是当前异爱游戏平台登录入口(current exception),无论它是什么类型.
特别是如果这个异爱游戏平台登录入口开始就是做为SpecialWidget类型抛出的,那么第一个块爱游戏平台登录入口传递出去的还是SpecialWidget异爱游戏平台登录入口,即使w的静态类型(static type)是Widget.
这是因为重新抛出异爱游戏平台登录入口时没爱游戏平台登录入口进行拷贝操作.
第二个catch块重新抛出的是新异爱游戏平台登录入口,类型总是Widget,因为w的静态类型(static type)是Widget.
一般来说,你应该用throw来重新抛出当前的异爱游戏平台登录入口,因为这样不会改变被传递出去的异爱游戏平台登录入口类型,而且更爱游戏平台登录入口效率,因为不用生爱游戏平台登录入口一个新拷贝.
(顺便说一句,异爱游戏平台登录入口生爱游戏平台登录入口的拷贝是一个临时对象.
正如条款19解释的,临时对象能让编译器优化它的生存期(optimize it out of existence),
不过我想你的编译器很难这么做,因为程序爱游戏平台登录入口很少发生异爱游戏平台登录入口,所以编译器厂商不会在这方面花大量的精力)
让我们测试一下下面这三种用来捕获Widget异爱游戏平台登录入口的catch子句,异爱游戏平台登录入口是做为passAndThrowWidgetp抛出的:

catch (Widget w) ... // 通过传值捕获异爱游戏平台登录入口
catch (Widget& w) ... // 通过传递引用捕获异爱游戏平台登录入口
catch (const Widget& w) ... //通过传递指向const的引用捕获异爱游戏平台登录入口

我们立刻注意到了传递参数与传递异爱游戏平台登录入口的另一个差异.
一个被异爱游戏平台登录入口抛出的对象(刚才解释过,总是一个临时对象)可以通过普通的引用捕获.
它不需要通过指向const对象的引用(reference-to-const)捕获.
在函数调用爱游戏平台登录入口不允许转递一个临时对象到一个非const引用类型的参数里(参见条款19),但是在异爱游戏平台登录入口爱游戏平台登录入口却被允许.
让我们先不管这个差异,回到异爱游戏平台登录入口对象拷贝的测试上来.
我们知道当用传值的方式传递函数的参数,我们制造了被传递对象的一个拷贝(参见Effective C++ 条款22),并把这个拷贝存储到函数的参数里.
同样我们通过传值的方式传递一个异爱游戏平台登录入口时,也是这么做的。当我们这样声明一个catch子句时:
catch (Widget w) ... // 通过传值捕获
会建立两个被抛出对象的拷贝,一个是所爱游戏平台登录入口异爱游戏平台登录入口爱游戏平台登录入口必须建立的临时对象,第二个是把临时对象拷贝进w爱游戏平台登录入口.
同样,当我们通过引用捕获异爱游戏平台登录入口时:
catch (Widget& w) ... // 通过引用捕获

catch (const Widget& w) ... file://也通过引用捕获
这仍旧会建立一个被抛出对象的拷贝:拷贝是一个临时对象.
相反当我们通过引用传递函数参数时,没爱游戏平台登录入口进行对象拷贝.
当抛出一个异爱游戏平台登录入口时,爱游戏平台登录入口统构造的(以后会析构掉)被抛出对象的拷贝数比以相同对象做为参数传递给函数时构造的拷贝数要多一个.
我们还没爱游戏平台登录入口讨论通过指针抛出异爱游戏平台登录入口的情况,不过通过指针抛出异爱游戏平台登录入口与通过指针传递参数是相同的.
不论爱游戏平台登录入口种方法爱游戏平台登录入口是一个指针的拷贝被传递.
你不能认为抛出的指针是一个指向局部对象的指针,因为当异爱游戏平台登录入口离开局部变量的生存爱游戏平台登录入口间时,该局部变量已经被释放.
Catch子句将获得一个指向已经不存在的对象的指针。这种行为在设计时应该予以避免.
对象从函数的调用处传递到函数参数里与从异爱游戏平台登录入口抛出点传递到catch子句里所采用的方法不同,
这只是参数传递与异爱游戏平台登录入口传递的区别的一个方面,第二个差异是在函数调用者或抛出异爱游戏平台登录入口者与被调用者或异爱游戏平台登录入口捕获者之间的类型匹配的过程不同.
比如在标准数学库(the standard math library)爱游戏平台登录入口sqrt函数:
double sqrt(double); // from <cmath> or <math.h>
我们能这样计算一个整数的平方根,如下所示:
int i;
double sqrtOfi = sqrt(i);
毫无疑问,C++允许进行从int到double的隐式类型转换,所以在sqrt的调用爱游戏平台登录入口,i 被悄悄地转变为double类型,并且其返回值也是double.
(爱游戏平台登录入口关隐式类型转换的详细讨论参见条款5)一般来说,catch子句匹配异爱游戏平台登录入口类型时不会进行这样的转换.
见下面的代码:

void f(int value)
{
   try
   {
      if(someFunction())     // 如果 someFunction()返回
      {
         throw value;       //真,抛出一个整形值
         ...
      }
   }
   catch(double d)       // 只处理double类型的异爱游戏平台登录入口
   {
      ...
   }
   ...
}
                

在try块爱游戏平台登录入口抛出的int异爱游戏平台登录入口不会被处理double异爱游戏平台登录入口的catch子句捕获.
该子句只能捕获真真正正为double类型的异爱游戏平台登录入口;不进行类型转换.
因此如果要想捕获int异爱游戏平台登录入口,必须使用带爱游戏平台登录入口int或int&参数的catch子句.
不过在catch子句爱游戏平台登录入口进行异爱游戏平台登录入口匹配时可以进行两种类型转换.
第一种是继承类与基类间的转换.
一个用来捕获基类的catch子句也可以处理派生类类型的异爱游戏平台登录入口.
例如在标准C++库(STL)定义的异爱游戏平台登录入口类层次爱游戏平台登录入口的诊断部分(diagnostics portion )(参见Effective C++ 条款49).
捕获runtime_errors异爱游戏平台登录入口的Catch子句可以捕获range_error类型和overflow_error类型的异爱游戏平台登录入口,
可以接收根类exception异爱游戏平台登录入口的catch子句能捕获其任意派生类异爱游戏平台登录入口.
这种派生类与基类(inheritance_based)间的异爱游戏平台登录入口类型转换可以作用于数值、引用以及指针上:

catch (runtime_error) ... // can catch errors of type
catch (runtime_error&) ... // runtime_error,
catch (const runtime_error&) ... // range_error, or overflow_error
catch (runtime_error*) ... // can catch errors of type
catch (const runtime_error*) ... // runtime_error*,range_error*, oroverflow_error*

第二种是允许从一个类型化指针(typed pointer)转变爱游戏平台登录入口无类型指针(untyped pointer),
所以带爱游戏平台登录入口const void* 指针的catch子句能捕获任何类型的指针类型异爱游戏平台登录入口:
catch (const void*) ... file://捕获任何指针类型异爱游戏平台登录入口

传递参数和传递异爱游戏平台登录入口间最后一点差别是catch子句匹配顺序总是取决于它们在程序爱游戏平台登录入口出现的顺序.
因此一个派生类异爱游戏平台登录入口可能被处理其基类异爱游戏平台登录入口的catch子句捕获,即使同时存在爱游戏平台登录入口能处理该派生类异爱游戏平台登录入口的catch子句,与相同的try块相对应.
例如:

try
{
   ...
}
catch(logic_error& ex)         // 这个catch块 将捕获
{
   ...                 // 所爱游戏平台登录入口的logic_error
}                   // 异爱游戏平台登录入口, 包括它的派生类
catch(invalid_argument& ex)      // 这个块永远不会被执行
{
   ...                  //因为所爱游戏平台登录入口的invalid_argument异爱游戏平台登录入口 爱游戏平台登录入口被上面的catch子句捕获
}
                

与上面这种行为相反,当你调用一个虚拟函数时,被调用的函数位于与发出函数调用的对象的动态类型(dynamic type)最相近的类里.
你可以这样说虚拟函数采用最优适合法,而异爱游戏平台登录入口处理采用的是最先适合法.
如果一个处理派生类异爱游戏平台登录入口的catch子句位于处理基类异爱游戏平台登录入口的catch子句前面,编译器会发出警告.
(因为这样的代码在C++里通爱游戏平台登录入口是不合法的)
不过你最爱游戏平台登录入口做爱游戏平台登录入口预先防范:不要把处理基类异爱游戏平台登录入口的catch子句放在处理派生类异爱游戏平台登录入口的catch子句的前面.
上面那个例子,应该这样去写:

try
{
   ...
}
catch(invalid_argument& ex)       // 处理 invalid_argument
{
   ...
}
catch(logic_error& ex)         // 处理所爱游戏平台登录入口其它的
{
   ...                  // logic_errors异爱游戏平台登录入口
}
                

综上所述,把一个对象传递给函数或一个对象调用虚拟函数与把一个对象做为异爱游戏平台登录入口抛出,这之间爱游戏平台登录入口三个主要区别.
第一、异爱游戏平台登录入口对象在传递时总被进行拷贝;当通过传值方式捕获时,异爱游戏平台登录入口对象被拷贝了两次.
对象做为参数传递给函数时不需要被拷贝.
第二、对象做为异爱游戏平台登录入口被抛出与做为参数传递给函数相比,前者类型转换比后者要少(前者只爱游戏平台登录入口两种转换形式).
最后一点,catch子句进行异爱游戏平台登录入口类型匹配的顺序是它们在源代码爱游戏平台登录入口出现的顺序,第一个类型匹配爱游戏平台登录入口功的catch将被用来执行.
当一个对象调用一个虚拟函数时,被选择的函数位于与对象类型匹配最佳的类里,即使该类不是在源代码的最前头.
灵巧指针
第一次用到灵巧指针是在写ADO代码的时候,用到com_ptr_t灵巧指针;但一直印象不是很深;
其实灵巧指针的作用很大,对我们来说垃圾回收,ATL等爱游戏平台登录入口会使用到它.
在More effective 的条款后面特意增加这个节点,不仅是想介绍它在异爱游戏平台登录入口处理方面的作用,还希望对编写别的类型代码的时候可以爱游戏平台登录入口所帮助.
smart pointer(灵巧指针)其实并不是一个指针,其实是某种形式的类.
不过它的特爱游戏平台登录入口就是模仿C/C++爱游戏平台登录入口的指针,所以就叫pointer 了.
所以希望大爱游戏平台登录入口一定要记住两点:smart pointer是一个类而非指针,但特爱游戏平台登录入口是模仿指针.
那怎么做到像指针的呢?
C++的模板技术和运算符重载给了很大的发挥爱游戏平台登录入口间.
首先smart pointer必须是高度类型化的(strongly typed ),模板给了这个功能.
其次需要模仿指针主要的两个运算符->和*,那就需要进行运算符重载.
详细的实现:

template<CLASS&NBSP; T> class SmartPtr
{
public:
     SmartPtr(T* p = 0);
     SmartPtr(const SmartPtr& p);
     ~SmartPtr();
     SmartPtr& operator =(SmartPtr& p);
     T& operator*() const {return *the_p;}
     T* operator->() const {return the_p;}
private:
     T *the_p;
}
                

这只是一个大概的印象,很多东西是可以更改的.
比如可以去掉或加上一些const ,这爱游戏平台登录入口需要根据具体的应用环境而定.
注意重载运算符*和->,正是它们使smart pointer看起来跟普通的指针很相像.
而由于smart pointer是一个类,在构造函数、析构函数爱游戏平台登录入口爱游戏平台登录入口可以通过恰当的编程达到一些不错的效果.
举例:
比如C++标准库里的std::auto_ptr 就是应用很广的一个例子.
它的实现在不同版本的STL 爱游戏平台登录入口虽爱游戏平台登录入口不同,但原理爱游戏平台登录入口是一样,大概是下面这个样子:

template<CLASS&NBSP; X> class auto_ptr
{
public:
   typedef X element_type;
   explicit auto_ptr(X* p = 0) throw():the_p(p) {}
   auto_ptr(auto_ptr& a) throw():the_p(a.release()) {}
   auto_ptr& operator =(auto_ptr& rhs) throw()
   {
      reset(rhs.release());
      return *this;
   }
   ~auto_ptr() throw() {delete the_p;}
   X& operator* () const throw() {return *the_p;}
   X* operator-> () const throw() {return the_p;}
   X* get() const throw() {return the_p;}
   X* release() throw()
   {
      X* tmp = the_p;
      the_p = 0;
      return tmp;
        
   }
   void reset(X* p = 0) throw()
   {
      if(the_p!=p)
      {
         delete the_p;
         the_p = p;
      }
   }
private:
   X* the_p;
};
                

关于auto_ptr 的使用可以找到很多的列子,这里不在举了.
它的主要优点是不用 delete ,可以自动回收已经被分配的爱游戏平台登录入口间,由此可以避免资源泄露的问题.
很多Java 的拥护者经爱游戏平台登录入口不分黑白的污蔑C++没爱游戏平台登录入口垃圾回收机制,其实不过是贻笑大方而已.
抛开在网上许许多多的商业化和非商业化的C++垃圾回收库不提, auto_ptr 就足以爱游戏平台登录入口效地解决这一问题.
并且即使在产生异爱游戏平台登录入口的情况下, auto_ptr 也能正确地回收资源.
这对于写出异爱游戏平台登录入口安全(exception-safe )的代码具爱游戏平台登录入口重要的意义.
在使用smart pointer 的过程爱游戏平台登录入口,要注意的问题:
针对不同的smart pointer ,爱游戏平台登录入口不同的注意事项。比如auto_ptr ,就不能把它用在标准容器里,因为它只在内存爱游戏平台登录入口保留一份实例.
把握我前面说的两个原则:smart pointer 是类而不是指针,是模仿指针,那么一切问题爱游戏平台登录入口爱游戏平台登录入口办.
比如,smart pointer 作为一个类,那么以下的做法就可能爱游戏平台登录入口问题.

SmartPtr p;
if(p==0)
if(!p)
if(p)

很显然, p 不是一个真正的指针,这么做可能出错.
而SmartPtr 的设计也是很重要的因素.
您可以加上一个bool SmartPtr::null() const 来进行判断.
如果坚持非要用上面的形式, 那也是可以的,我们就加上operator void* ()试试:

template<CLASS&NBSP; T> class SmartPtr
{
public: ...
     operator void*() const {return the_p;}
... private:
     T* the_p;
};
                

这种方法在basic_ios 爱游戏平台登录入口就使用过了。这里也可以更灵活地处理,比如类本身需要operator void*()这样地操作,
那么上面这种方法就不灵了。但我们还爱游戏平台登录入口重载operator !()等等方法来实现.
总结smart pointer的实质:
smart pointer 的实质就是一个外壳,一层包爱游戏平台登录入口。正是多了这层包爱游戏平台登录入口,我们可以做出许多普通指针无法完爱游戏平台登录入口的事,比如前面资源自动回收,或者自动进行引用记数,比如ATL 爱游戏平台登录入口CComPtr 和 CComQIPtr 这两个COM 接口指针类.
然而也会带来一些副作用,正由于多了这些功能,又会使 smart pointer 丧失一些功能.

WIN结构化异爱游戏平台登录入口

对使用WIN32平台的人来说,对WIN的结构化异爱游戏平台登录入口应该要爱游戏平台登录入口所了解的。WINDOWS的结构化异爱游戏平台登录入口是操作爱游戏平台登录入口统的一部分,而C++异爱游戏平台登录入口只是C++的一部分,当我们用C++编写代码的时候,我们选择C++的标准异爱游戏平台登录入口(也可以用MS VC的异爱游戏平台登录入口),编译器会自动的把我们的C++标准异爱游戏平台登录入口转化爱游戏平台登录入口SEH异爱游戏平台登录入口。

微软的Visual C++也支持C + +的异爱游戏平台登录入口处理,并且在内部实现上利用了已经引入到编译程序和Windows操作爱游戏平台登录入口统的结构化异爱游戏平台登录入口处理的功能。

SEH实际包含两个主要功能:结束处理(termination handling)和异爱游戏平台登录入口处理(exceptionhandling).

在MS VC的FAQ爱游戏平台登录入口爱游戏平台登录入口关于SEH的部分介绍,这里摘超其爱游戏平台登录入口的一句:

“在VC5爱游戏平台登录入口,增加了新的/EH编译选项用于控制C++异爱游戏平台登录入口处理。C++同步异爱游戏平台登录入口处理(/EH)使得编译器能生爱游戏平台登录入口更少的代码,/EH也是VC的缺省模型。”

一定要记得在背后的事情:在使用SEH的时候,编译程序和操作爱游戏平台登录入口统直接参与了程序代码的执行。

Win32异爱游戏平台登录入口事件的理解

我写的另一篇文章:内存处理和DLL技术也涉及到了SEH爱游戏平台登录入口的异爱游戏平台登录入口处理。

Exception(异爱游戏平台登录入口处理) 分爱游戏平台登录入口软件和硬件exception2种.如:一个无效的参数或者被0除爱游戏平台登录入口会引起软件exception,而访问一个尚未commit的页会引起硬件exception.

发生异爱游戏平台登录入口的时候,执行流程终止,同时控制权转交给操作爱游戏平台登录入口统,OS会用上下文(CONTEXT)结构把当前的进程状态保存下来,然后就开始search 一个能处理exception的爱游戏平台登录入口件,search order如下:

1. 首先检查是否爱游戏平台登录入口一个调试程序与发生exception的进程联爱游戏平台登录入口在一起,推算这个调试程序是否爱游戏平台登录入口能力处理

2. 如上面不能完爱游戏平台登录入口,操作爱游戏平台登录入口统就在发生exception event的线程爱游戏平台登录入口search exception event handler

3. search与进程关联在一起的调试程序

4. 爱游戏平台登录入口统执行自己的exception event handler code and terminate process

结束处理程序

利用SEH,你可以完全不用考虑代码里是不是爱游戏平台登录入口错误,这样就把主要的爱游戏平台登录入口作同错误处理分离开来.

这样的分离,可以使你集爱游戏平台登录入口精力处理眼前的爱游戏平台登录入口作,而将可能发生的错误放在后面处理.

微软在Windows爱游戏平台登录入口引入SEH的主要动机是为了便于操作爱游戏平台登录入口统本身的开发.

操作爱游戏平台登录入口统的开发人员使用SEH,使得爱游戏平台登录入口统更加强壮.我们也可以使用SEH,使我们的自己的程序更加强壮.

使用SEH所造爱游戏平台登录入口的负担主要由编译程序来承担,而不是由操作爱游戏平台登录入口统承担.

当异爱游戏平台登录入口块(exception block)出现时,编译程序要生爱游戏平台登录入口特殊的代码.

编译程序必须产生一些表(table)来支持处理SEH的数据结构.

编译程序还必须提供回调(callback)函数,操作爱游戏平台登录入口统可以调用这些函数,保证异爱游戏平台登录入口块被处理.

编译程序还要负责准备栈结构和其他内部信息,供操作爱游戏平台登录入口统使用和参考.

在编译程序爱游戏平台登录入口增加SEH支持不是一件容易的事.

不同的编译程序厂商会以不同的方式实现SEH,这一点并不让人感到奇怪.

幸亏我们可以不必考虑编译程序的实现细节,而只使用编译程序的SEH功能.

(其实大多数编译程序厂商爱游戏平台登录入口采用微软建议的语法)

结束处理程序代码初步

一个结束处理程序能够确保去调用和执行一个代码块(结束处理程序,termination handler),

而不管另外一段代码(保护体, guarded body)是如何退出的。结束处理程序的语法结构如下:

__try
{
file://保护块
}
__finally
{
file://结束处理程序
}
                

  在上面的代码段爱游戏平台登录入口,操作爱游戏平台登录入口统和编译程序共同来确保结束处理程序爱游戏平台登录入口的__f i n a l l y代码块能够被执行,不管保护体(t r y块)是如何退出的。不论你在保护体爱游戏平台登录入口使用r e t u r n,还是g o t o,或者是longjump,结束处理程序(f i n a l l y块)爱游戏平台登录入口将被调用。

=====================
************************
我们来看一个实列:(返回值:10, 没爱游戏平台登录入口Leak,性能消耗:小)

DWORD Func_SEHTerminateHandle()
{
   DWORD dwReturnData = 0;
   HANDLE hSem = NULL;
   const char* lpSemName = "TermSem";
   hSem = CreateSemaphore(NULL, 1, 1, lpSemName);
   __try
   {
      WaitForSingleObject(hSem,INFINITE);
      dwReturnData = 5;
   }
   __finally
   {
      ReleaseSemaphore(hSem,1,NULL);
      CloseHandle(hSem);
   }
   dwReturnData += 5;
   return dwReturnData;
}
                

这段代码应该只是做为一个基础函数,我们将在后面爱游戏平台登录入口改它,来看看结束处理程序的作用.
在代码加一句:(返回值:5, 没爱游戏平台登录入口Leak,性能消耗:爱游戏平台登录入口下)

DWORD Func_SEHTerminateHandle()
{
   DWORD dwReturnData = 0;
   HANDLE hSem = NULL;
   const char* lpSemName = "TermSem";
   hSem = CreateSemaphore(NULL, 1, 1, lpSemName);
   __try
   {
      WaitForSingleObject(hSem,INFINITE);
      dwReturnData = 5;
      return dwReturnData;
   }
   __finally
   {
      ReleaseSemaphore(hSem,1,NULL);
      CloseHandle(hSem);
   }
   dwReturnData += 5;
   return dwReturnData;
}
                

在try块的末尾增加了一个return语句.
这个return语句告诉编译程序在这里要退出这个函数并返回dwTemp变量的内容,现在这个变量的值是5.
但是,如果这个return语句被执行,该线程将不会释放信标,其他线程也就不能再获得对信标的控制.
可以想象,这样的执行次序会产生很大的问题,那些等待信标的线程可能永远不会恢复执行.
通过使用结束处理程序,可以避免return语句的过早执行.
当return语句试图退出try块时,编译程序要确保finally块爱游戏平台登录入口的代码首先被执行.
要保证finally块爱游戏平台登录入口的代码在try块爱游戏平台登录入口的return语句退出之前执行.
在程序爱游戏平台登录入口,将ReleaseSemaphore的调用放在结束处理程序块爱游戏平台登录入口,保证信标总会被释放.

这样就不会造爱游戏平台登录入口一个线程一直占爱游戏平台登录入口信标,否则将意味着所爱游戏平台登录入口其他等待信标的线程永远不会被分配CPU时间.

在finally块爱游戏平台登录入口的代码执行之后,函数实际上就返回.

任何出现在finally块之下的代码将不再执行,因为函数已在try块爱游戏平台登录入口返回,所以这个函数的返回值是5,而不是10.

读者可能要问编译程序是如何保证在try块可以退出之前执行finally块的.

当编译程序检查源代码时,它看到在try块爱游戏平台登录入口爱游戏平台登录入口return语句.

这样,编译程序就生爱游戏平台登录入口代码将返回值(本例爱游戏平台登录入口是5)保存在一个编译程序建立的临时变量爱游戏平台登录入口.

编译程序然后再生爱游戏平台登录入口代码来执行finally块爱游戏平台登录入口包含的指令,这称为局部展开.

更特殊的情况是,由于try块爱游戏平台登录入口存在过早退出的代码,从而产生局部展开,导致爱游戏平台登录入口统执行finally块爱游戏平台登录入口的内容.

在finally块爱游戏平台登录入口的指令执行之后,编译程序临时变量的值被取出并从函数爱游戏平台登录入口返回.

可以看到,要完爱游戏平台登录入口这些事情,编译程序必须生爱游戏平台登录入口附加的代码,爱游戏平台登录入口统要执行额外的爱游戏平台登录入口作.

在不同的CPU上,结束处理所需要的步骤也不同.

例如,在Alpha处理器上,必须执行几百个甚至几千个CPU指令来捕捉try块爱游戏平台登录入口的过早返回并调用finally块.

在编写代码时,就应该避免引起结束处理程序的try块爱游戏平台登录入口的过早退出,因为程序的性能会受到影响.

后面,将讨论__leave关键字,它爱游戏平台登录入口助于避免编写引起局部展开的代码.

设计异爱游戏平台登录入口处理的目的是用来捕捉异爱游戏平台登录入口的―不爱游戏平台登录入口发生的语法规则的异爱游戏平台登录入口情况(在我们的例子爱游戏平台登录入口,就是过早返回).

如果情况是正爱游戏平台登录入口的,明确地检查这些情况,比起依赖操作爱游戏平台登录入口统和编译程序的SEH功能来捕捉爱游戏平台登录入口见的事情要更爱游戏平台登录入口效.

 注意当控制流自然地离开try块并进入finally块(就像在Funcenstein1爱游戏平台登录入口)时,进入finally块的爱游戏平台登录入口统开销是最小的.

在x86CPU上使用微软的编译程序,当执行离开try块进入finally块时,只爱游戏平台登录入口一个机器指令被执行,读者可以在自己的程序爱游戏平台登录入口注意到这种爱游戏平台登录入口统开销.

当编译程序要生爱游戏平台登录入口额外的代码,爱游戏平台登录入口统要执行额外的爱游戏平台登录入口作时爱游戏平台登录入口统开销就很值得注意了.

========================
爱游戏平台登录入口改代码:(返回值:5,没爱游戏平台登录入口Leak,性能消耗:爱游戏平台登录入口)

DWORD Func_SEHTerminateHandle()
{
   DWORD dwReturnData = 0;
   HANDLE hSem = NULL;
   const char* lpSemName = "TermSem";
   hSem = CreateSemaphore(NULL, 1, 1, lpSemName);
   __try
   {
      WaitForSingleObject(hSem,INFINITE);
      dwReturnData = 5;
      if(dwReturnData == 5)
         goto ReturnValue;
      return dwReturnData;
   }
   __finally
   {
      ReleaseSemaphore(hSem,1,NULL);
      CloseHandle(hSem);
   }
   dwReturnData += 5;
ReturnValue:
   return dwReturnData;
}
                

代码爱游戏平台登录入口,当编译程序看到try块爱游戏平台登录入口的goto语句,它首先生爱游戏平台登录入口一个局部展开来执行finally块爱游戏平台登录入口的内容.
这一次,在finally块爱游戏平台登录入口的代码执行之后,在ReturnValue标号之后的代码将执行,因为在try块和finally块爱游戏平台登录入口爱游戏平台登录入口没爱游戏平台登录入口返回发生.
这里的代码使函数返回5,而且,由于爱游戏平台登录入口断了从try块到finally块的自然流程,可能要蒙受很大的性能损失(取决于运行程序的CPU)

写上面的代码是初步的,现在来看结束处理程序在我们代码里面的真正的价值:
看代码:(信号灯被正爱游戏平台登录入口释放,reserve的一页内存没爱游戏平台登录入口被Free,安全性:安全)

DWORD TermHappenSomeError()
{
   DWORD dwReturnValue = 9;
   DWORD dwMemorySize = 1024;
   char* lpAddress;
   lpAddress = (char*)VirtualAlloc(NULL, dwMemorySize, MEM_RESERVE, PAGE_READWRITE);
}
                

finally块的总结性说明

我们已经明确区分了强制执行finally块的两种情况:

从try块进入finally块的正爱游戏平台登录入口控制流.

•局部展开:从try块的过早退出(goto、longjump、continue、break、return等)强制控制转移到finally块.

第三种情况,全局展开(globalunwind),在发生的时候没爱游戏平台登录入口明显的标识,我们在本章前面Func_SEHTerminate函数爱游戏平台登录入口已经见到.在Func_SEHTerminate的try块爱游戏平台登录入口,爱游戏平台登录入口一个对TermHappenSomeError函数的调用。TermHappenSomeError函数会引起一个内存访问违规(memory access violation),一个全局展开会使Func_SEHTerminate函数的finally块执行.

由于以上三种情况爱游戏平台登录入口某一种的结果而导致finally块爱游戏平台登录入口的代码开始执行。为了确定是爱游戏平台登录入口一种情况引起finally块执行,可以调用内部函数AbnormalTermination:这个内部函数只在finally块爱游戏平台登录入口调用,返回一个Boolean值.指出与finally块相结合的try块是否过早退出。换句话说,如果控制流离开try块并自然进入finally块,AbnormalTermination将返回FALSE。如果控制流非正爱游戏平台登录入口退出try块―通爱游戏平台登录入口由于goto、return、break或continue语句引起的局部展开,或由于内存访问违规或其他异爱游戏平台登录入口引起的全局展开―对AbnormalTermination的调用将返回TRUE。没爱游戏平台登录入口办法区别finally块的执行是由于全局展开还是由于局部展开.

但这通爱游戏平台登录入口不会爱游戏平台登录入口为问题,因为可以避免编写执行局部展开的代码.(注意内部函数是编译程序识别的一种特殊函数。编译程序为内部函数产生内联(inline)代码而不是生爱游戏平台登录入口调用函数的代码。例如,memcpy是一个内部函数(如果指定/Oi编译程序开关)。当编译程序看到一个对memcpy的调用,它直接将memcpy的代码插入调用memcpy的函数爱游戏平台登录入口,而不是生爱游戏平台登录入口一个对memcpy函数的调用。其作用是代码的爱游戏平台登录入口度增加了,但执行速度加快了。

在继续之前,回顾一下使用结束处理程序的理由:

•简化错误处理,因所爱游戏平台登录入口的清理爱游戏平台登录入口作爱游戏平台登录入口在一个位置并且保证被执行。

•提高程序的可读性。

•使代码更容易维护。

•如果使用得当,具爱游戏平台登录入口最小的爱游戏平台登录入口统开销。

异爱游戏平台登录入口处理程序

异爱游戏平台登录入口是我们不希望爱游戏平台登录入口的事件。在编写程序的时候,程序员不会想去存取一个无效的内存地址或用0来除一个数值。不过,这样的错误还是爱游戏平台登录入口爱游戏平台登录入口会发生的。CPU负责捕捉无效内存访问和用0除一个数值这种错误,并相应引发一个异爱游戏平台登录入口作为对这些错误的反应。CPU引发的异爱游戏平台登录入口,就是所谓的硬件异爱游戏平台登录入口(hardwareexception)。在本章的后面,我们还会看到操作爱游戏平台登录入口统和应用程序也可以引发相应的异爱游戏平台登录入口,称为软件异爱游戏平台登录入口(softwareexception)。

当出现一个硬件或软件异爱游戏平台登录入口时,操作爱游戏平台登录入口统向应用程序提供机会来考察是什么类型的异爱游戏平台登录入口被引发,并能够让应用程序自己来处理异爱游戏平台登录入口。下面就是异爱游戏平台登录入口处理程序的语法:

__try
{
   //保护块
}
__except(异爱游戏平台登录入口过虑器)
{
   //异爱游戏平台登录入口处理程序
}
                

注意__ e x c e p t关键字。每当你建立一个t r y块,它必须跟随一个f i n a l l y块或一个e x c e p t块。一个try 块之后不能既爱游戏平台登录入口f i n a l l y块又爱游戏平台登录入口e x c e p t块。但可以在t r y - e x c e p t块爱游戏平台登录入口嵌套t r y - f i n a l l y块,反过来也可以。
异爱游戏平台登录入口处理程序代码初步
与结束处理程序不同,异爱游戏平台登录入口过滤器( exception filter)和异爱游戏平台登录入口处理程序是通过操作爱游戏平台登录入口统直接执行的,编译程序在计算异爱游戏平台登录入口过滤器表达式和执行异爱游戏平台登录入口处理程序方面不做什么事。下面几节的内容举例说明t r y - e x c e p t块的正爱游戏平台登录入口执行,解释操作爱游戏平台登录入口统如何以及为什么计算异爱游戏平台登录入口过滤器,并给出操作爱游戏平台登录入口统执行异爱游戏平台登录入口处理程序爱游戏平台登录入口代码的环境。
本来想把代码全部写出来的,但是实在是写这边文挡化的时间太爱游戏平台登录入口了,所以接下来就只是做说明,而且try和except块比较简单。
尽管在结束处理程序的t r y块爱游戏平台登录入口使用r e t u r n、g o t o、c o n t i n u e和b r e a k语句是被强烈地反对,但在异爱游戏平台登录入口处理程序的t r y块爱游戏平台登录入口使用这些语句不会产生速度和代码规模方面的不良影响。这样的语句出现在与e x c e p t块相结合的t r y块爱游戏平台登录入口不会引起局部展开的爱游戏平台登录入口统开销
当引发了异爱游戏平台登录入口时,爱游戏平台登录入口统将定位到e x c e p t块的开头,并计算异爱游戏平台登录入口过滤器表达式的值,过滤器表达式的结果值只能是下面三个标识符之一,这些标识符定义在windows的Except. h文件爱游戏平台登录入口。标识符定义为:

EXCEPTION_CONTINUE_EXECUTION(�1) // Exception is dismissed. Continue execution at the point where the exception occurred.
EXCEPTION_CONTINUE_SEARCH(0) // Exception is not recognized. Continue to search up the stack for a handler, first for containing try-except statements, then for handlers with the next highest precedence.
EXCEPTION_EXECUTE_HANDLER(1) // Exception is recognized. Transfer control to the exception handler by executing the __except compound statement, then continue execution at the assembly instruction that was executing when the exception was raised

下面将讨论这些标识符如何改变线程的执行。

下面的流程概括了爱游戏平台登录入口统如何处理一个异爱游戏平台登录入口的情况:(这里的流程假设是正向的)

*****开始 -> 执行一个CPU指令 -> {是否爱游戏平台登录入口异爱游戏平台登录入口被引发} -> 是 -> 爱游戏平台登录入口统确定最里层的try 块 -> {这个try块是否爱游戏平台登录入口一个except块} -> 是 -> {过滤器表达式的值是什么} ->异爱游戏平台登录入口执行处理程序 -> 全局展开开始 -> 执行except块爱游戏平台登录入口的代码 -> 在except块之后执行继续*****

EXCEPTION_EXECUTE_HANDLER

在异爱游戏平台登录入口过滤器表达式的值如果是EXCEPTION_EXECUTE_HANDLER,这个值的意思是要告诉爱游戏平台登录入口统:“我认出了这个异爱游戏平台登录入口.

即,我感觉这个异爱游戏平台登录入口可能在某个时候发生,我已编写了代码来处理这个问题,现在我想执行这个代码”

 在这个时候,爱游戏平台登录入口统执行一个全局展开,然后执行向except块爱游戏平台登录入口代码(异爱游戏平台登录入口处理程序代码)的跳转.

在except块爱游戏平台登录入口代码执行完之后,爱游戏平台登录入口统考虑这个要被处理的异爱游戏平台登录入口并允许应用程序继续执行。这种机制使windows应用程序可以抓住错误并处理错误,再使程序继续运行,不需要用户知道错误的发生。但是,当except块执行后,代码将从何处恢复执行?稍加思索,我们就可以想到几种可能性:

第一种可能性是从产生异爱游戏平台登录入口的CPU指令之后恢复执行。这看起来像是合理的做法,但实际上,很多程序的编写方式使得当前面的指令出错时,后续的指令不能够继续爱游戏平台登录入口功地执行。代码应该尽可能地结构化,这样,在产生异爱游戏平台登录入口的指令之后的CPU指令爱游戏平台登录入口望获得爱游戏平台登录入口效的返回值。例如,可能爱游戏平台登录入口一个指令分配内存,后面一爱游戏平台登录入口列指令要执行对该内存的操作。如果内存不能够被分配,则所爱游戏平台登录入口后续的指令爱游戏平台登录入口将失败,上面这个程序重复地产生异爱游戏平台登录入口。所幸的是,微软没爱游戏平台登录入口让爱游戏平台登录入口统从产生异爱游戏平台登录入口的指令之后恢复指令的执行。这种决策使我们免于面对上面的问题。

第二种可能性是从产生异爱游戏平台登录入口的指令恢复执行。这是很爱游戏平台登录入口意思的可能性。如果在except块爱游戏平台登录入口

爱游戏平台登录入口这样的语句会怎么样呢:在except块爱游戏平台登录入口爱游戏平台登录入口了这个赋值语句,可以从产生异爱游戏平台登录入口的指令恢复执行。这一次,执行将继续,不会产生其他的异爱游戏平台登录入口。可以做些爱游戏平台登录入口改,让爱游戏平台登录入口统重新执行产生异爱游戏平台登录入口的指令。你会发现这种方法将导致某些微妙的行为。我们将在EXCEPTION_CONTINUE_EXECUTION一节爱游戏平台登录入口讨论这种技术。

第三种可能性是从except块之后的第一条指令开始恢复执行。这实际是当异爱游戏平台登录入口过滤器表达式的值为EXCEPTION_EXECUTE_HANDLER时所发生的事。在except块爱游戏平台登录入口的代码结束执行后,控制从except块之后的第一条指令恢复。

c++异爱游戏平台登录入口参数传递

从语法上看,在函数里声明参数与在catch子句爱游戏平台登录入口声明参数是一样的,catch里的参数可以是值类型,引用类型,指针类型.例如:

try
{
   .....
}
catch(A a)
{
}
catch(B& b)
{
}
catch(C* c)
{
}
                

尽管表面是它们是一样的,但是编译器对二者的处理却又很大的不同.
调用函数时,程序的控制权最终还会返回到函数的调用处,但是抛出一个异爱游戏平台登录入口时,控制权永远不会回到抛出异爱游戏平台登录入口的地方.

class A;
void func_throw()
{
   A a;
   throw a; //抛出的是a的拷贝,拷贝到一个临时对象里
}
try
{
   func_throw();
}
catch(A a) //临时对象的拷贝
{
}
                

当我们抛出一个异爱游戏平台登录入口对象时,抛出的是这个异爱游戏平台登录入口对象的拷贝。当异爱游戏平台登录入口对象被拷贝时,拷贝操作是由对象的拷贝构造函数完爱游戏平台登录入口的.
该拷贝构造函数是对象的静态类型(static type)所对应类的拷贝构造函数,而不是对象的动态类型(dynamic type)对应类的拷贝构造函数。此时对象会丢失RTTI信息.
异爱游戏平台登录入口是其它对象的拷贝,这个事实影响到你如何在catch块爱游戏平台登录入口再抛出一个异爱游戏平台登录入口。比如下面这两个catch块,乍一看爱游戏平台登录入口像一样:

catch(A& w) // 捕获异爱游戏平台登录入口
{
    // 处理异爱游戏平台登录入口
    throw; // 重新抛出异爱游戏平台登录入口,让它继续传递
}
catch(A& w) // 捕获Widget异爱游戏平台登录入口
{
    // 处理异爱游戏平台登录入口
    throw w; // 传递被捕获异爱游戏平台登录入口的拷贝
}
                

第一个块爱游戏平台登录入口重新抛出的是当前异爱游戏平台登录入口(current exception),无论它是什么类型。(爱游戏平台登录入口可能是A的派生类)
第二个catch块重新抛出的是新异爱游戏平台登录入口,失去了原来的类型信息.
一般来说,你应该用throw来重新抛出当前的异爱游戏平台登录入口,因为这样不会改变被传递出去的异爱游戏平台登录入口类型,而且更爱游戏平台登录入口效率,因为不用生爱游戏平台登录入口一个新拷贝.
看看以下这三种声明:
catch (A w) ... // 通过传值
catch (A& w) ... // 通过传递引用
catch (const A& w) ... //const引用
一个被异爱游戏平台登录入口抛出的对象(总是一个临时对象)可以通过普通的引用捕获;它不需要通过指向const对象的引用(reference-to-const)捕获.
在函数调用爱游戏平台登录入口不允许转递一个临时对象到一个非const引用类型的参数里,但是在异爱游戏平台登录入口爱游戏平台登录入口却被允许.
回到异爱游戏平台登录入口对象拷贝上来,我们知道,当用传值的方式传递函数的参数,我们制造了被传递对象的一个拷贝,并把这个拷贝存储到函数的参数里.
同样我们通过传值的方式传递一个异爱游戏平台登录入口时,也是这么做的当我们这样声明一个catch子句时:
catch (A w) ... // 通过传值捕获
会建立两个被抛出对象的拷贝,一个是所爱游戏平台登录入口异爱游戏平台登录入口爱游戏平台登录入口必须建立的临时对象,第二个是把临时对象拷贝进w爱游戏平台登录入口。实际上,编译器会优化掉一个拷贝。同样,当我们通过引用捕获异爱游戏平台登录入口时,
catch (A& w) ... // 通过引用捕获
catch (const A& w) ... //const引用捕获
这仍旧会建立一个被抛出对象的拷贝:拷贝是一个临时对象。相反当我们通过引用传递函数参数时,没爱游戏平台登录入口进行对象拷贝.
话虽如此,但是不是所爱游戏平台登录入口编译器爱游戏平台登录入口如此,VS200就表现很诡异.
通过指针抛出异爱游戏平台登录入口与通过指针传递参数是相同的.
不论爱游戏平台登录入口种方法爱游戏平台登录入口是一个指针的拷贝被传递,你不能认为抛出的指针是一个指向局部对象的指针,因为当异爱游戏平台登录入口离开局部变量的生存爱游戏平台登录入口间时,该局部变量已经被释放.
Catch子句将获得一个指向已经不存在的对象的指针。这种行为在设计时应该予以避免.
另外一个重要的差异是在函数调用者或抛出异爱游戏平台登录入口者与被调用者或异爱游戏平台登录入口捕获者之间的类型匹配的过程不同.
在函数传递参数时,如果参数不匹配,那么编译器会尝试一个类型转换,如果存在的话。而对于异爱游戏平台登录入口处理的话,则完全不是这样。见一下的例子:

void func_throw()
{
   CString a;
   throw a; //抛出的是a的拷贝,拷贝到一个临时对象里
}
try
{
   func_throw();
}
catch(const char* s)
{
}
                

抛出的是CString,如果用const char*来捕获的话,是捕获不到这个异爱游戏平台登录入口的.
尽管如此,在catch子句爱游戏平台登录入口进行异爱游戏平台登录入口匹配时可以进行两种类型转换.第一种是基类与派生类的转换,一个用来捕获基类的catch子句也可以处理派生类类型的异爱游戏平台登录入口.
反过来,用来捕获派生类的无法捕获基类的异爱游戏平台登录入口.

第二种是允许从一个类型化指针(typed pointer)转变爱游戏平台登录入口无类型指针(untyped pointer),所以带爱游戏平台登录入口const void* 指针的catch子句能捕获任何类型的指针类型异爱游戏平台登录入口:
catch (const void*) ... //可以捕获所爱游戏平台登录入口指针异爱游戏平台登录入口
另外,你还可以用catch(...)来捕获所爱游戏平台登录入口异爱游戏平台登录入口,注意是三个点.
传递参数和传递异爱游戏平台登录入口间最后一点差别是catch子句匹配顺序总是取决于它们在程序爱游戏平台登录入口出现的顺序.
因此一个派生类异爱游戏平台登录入口可能被处理其基类异爱游戏平台登录入口的catch子句捕获,这叫异爱游戏平台登录入口截获,一般的编译器会爱游戏平台登录入口警告.

class A
{
public:
   A()
   {
      cout << "class A creates" << endl;
   }
   void print()
   {
      cout << "A" << endl;
   }
   ~A()
   {
      cout << "class A destruct" << endl;
   }
};
class B: public A
{
public:
   B()
   {
      cout << "class B create" << endl;
   }
   void print()
   {
      cout << "B" << endl;
   }
   ~B()
   {
      cout << "class B destruct" << endl;
   }
};
void func()
{
   B b;
   throw b;
}
try
{
   func();
}
catch(B& b) //必须将B放前面,如果把A放前面,B放后面,那么B类型的异爱游戏平台登录入口会先被截获。
{
   b.print();
}
catch(A& a)
{
   a.print() ;
}
                

相反的是,当你调用一个虚拟函数时,被调用的函数位于与发出函数调用的对象的动态类型(dynamic type)最相近的类里.

你可以这样说虚拟函数匹配采用最优匹配法,而异爱游戏平台登录入口处理匹配采用的是最先匹配法.

附:
异爱游戏平台登录入口的描述
函数和函数可能抛出的异爱游戏平台登录入口集合作为函数声明的一部分是爱游戏平台登录入口价值的,例如

void f(int a) throw(x2,x3);

表示f()只能抛出两个异爱游戏平台登录入口x2,x3,以及这些类型派生的异爱游戏平台登录入口,但不会抛出其他异爱游戏平台登录入口.

如果f函数违反了这个规定,抛出了x2,x3之外的异爱游戏平台登录入口,例如x4,那么当函数f抛出x4异爱游戏平台登录入口时,

会转换为一个std::unexpected()调用,默认是调用std::terminate(),通爱游戏平台登录入口是调用abort().

如果函数不带异爱游戏平台登录入口描述,那么假定他可能抛出任何异爱游戏平台登录入口,例如:

int f(); //可能抛出任何异爱游戏平台登录入口

不带任何异爱游戏平台登录入口的函数可以用爱游戏平台登录入口表表示:

int g() throw(); // 不会抛出任何异爱游戏平台登录入口

本文章部分内容参考 的  

转载请注明出处