C与C++中的异常处理8

原创 2002年03月01日 08:54:00

1.     自动删除,类属newdeleteplacement new placement delete


    在上次结束时,我期望道:当一个新产生的对象在没有完全构造时,它所占用的内存能自动释放。很幸运,C++标准委员会将这个功能加入到了语言中(而不幸的是,这个特性加得太晚了,许多编译器还不支持它)。Visual C++ 56都支持这个“自动删除”特性(但,如我们将要看到的,Visual C++ 5的支持是不完全的)。


 


1.1     自动删除


    要实际验证它,在上次的例6中增加带跟踪信息的operator newoperator delete函数:


// Example 7


 


#include <iostream>


#include <memory>


#include <stdio.h>


#include <stdlib.h>


 


void *operator new(size_t const n)


   {


   printf("   ::operator new/n");


   return malloc(n);


   }


 


void operator delete(void *const p)


   {


   std::cout << "   ::operator delete" << std::endl;


   free(p);


   }


 


class B


   {


public:


   B(int const ID) : ID_(ID)


      {


      std::cout << ID_ << " B::B enter" << std::endl;


      if (ID_ > 2)


         {


         std::cout << std::endl;


         std::cout << "  THROW" << std::endl;


         std::cout << std::endl;


         throw 0;


         }


      std::cout << ID_ << " B::B exit" << std::endl;


      }


   ~B()


      {


      std::cout << ID_ << " B::~B" << std::endl;


      }


private:


   int const ID_;


   };


 


class A


   {


public:


   A() : b1(new B(1)), b2(new B(2)), b3(new B(3))


      {


      std::cout << "  A::A" << std::endl;


      }


   ~A()    


      {


      std::cout << "  A::~A" << std::endl;


      }


private:


   std::auto_ptr<B> const b1;


   std::auto_ptr<B> const b2;


   std::auto_ptr<B> const b3;


   };


 


int main()


   {


   try


      {


      A a;


      }


   catch(...)


      {


      std::cout << std::endl;


      std::cout << "  CATCH" << std::endl;


      std::cout << std::endl;


      }


   return 0;


   }


    程序将用我们自己的operator newoperator delete代替标准运行库提供的版本。这样,我们将能跟踪所有的动态创建对象时的分配和释放内存操作。(我同时小小修改了其它的跟踪信息,以便输出信息更容易读。)


    注意,我们的operator new调用了printf而不是std::cout。本来,我确实使用了std::cout,但程序在运行库中产生了一个无效页错误。调试器显示运行库在初始化std::cout前调用了operator new,而operator new又试图调用还没有初始化的std::cout,程序于是崩溃了。


    我在Visual C++ 6中运行程序,得到了头大的输出:


   ::operator new


   ::operator new


   ::operator new


   ::operator new


   ::operator new


   ::operator new


   ::operator delete


   ::operator delete


   ::operator new


   ::operator new


   ::operator new


   ::operator new


   ::operator new


   ::operator new


   ::operator delete


   ::operator delete


1 B::B enter


1 B::B exit


   ::operator new


2 B::B enter


2 B::B exit


   ::operator new


3 B::B enter


 


  THROW


 


   ::operator delete


2 B::~B


   ::operator delete


1 B::~B


   ::operator delete


 


  CATCH


 


   ::operator delete


   ::operator delete


   ::operator delete


   ::operator delete


   ::operator delete


Blech.


 


    我无法从中分辨出有用的信息。原因很简单:我们的代码,标准运行库的代码,以及编译器暗中生成的代码都调用了operator newoperator delete。我们需要一些方法来隔离出我们感兴趣的调用过程,并只输出它们的跟踪信息。


 


1.2     类属newdelete


    C++又救了我们。不用跟踪全局的operator newoperator delete,我们可以跟踪其类属版本。既然我们感兴趣的是B对象的分配和释放过程,我们只需将operator newoperator delete移到类B中去:


// Example 8


 


#include <iostream>


#include <memory>


 


class B


   {


public:


   void *operator new(size_t const n)


      {


      std::cout << "  B::operator new" << std::endl;


      return ::operator new(n);


      }


   void operator delete(void *const p)


      {


      std::cout << "  B::operator delete" << std::endl;


      operator delete(p);


      }


   // ... rest of class B unchanged


};


 


// ... class A and main unchanged


    编译器将为B的对象调用这些函数,而为其它对象的分配和释放调用标准运行库中的函数版本。


    通过在你自己的类这增加这样的局部操作函数,你可以更好的管理动态创建的此类型对象。例如,嵌入式系统的程序员经常在特殊映射的设备或快速内存中分配某些对象,通过其类型特有的operator newoperator delete,可以控制如何及在哪儿分配这些对象。


    对我们的例子,特殊的堆管理是没必要的。因此,我在类属operator new operator delete中调用了其全局版本而不再是mallocfree,并去除了对头文件<stdlib.h>的包含。这样,所有对象的分配和释放的实际语义保持了一致。


    同时,因为我们的operator new不在在全局范围内,它不会被运行库在构造std::cout前调用,于是我可以在其中安全地调用std::cout了。因为不再调用printf,我也去掉了<stdio.h>


    编译并运行例8。将发现输出信息有用多了:


  B::operator new


1 B::B enter


1 B::B exit


  B::operator new


2 B::B enter


2 B::B exit


  B::operator new


3 B::B enter


 


  THROW


 


  B::operator delete


2 B::~B


  B::operator delete


1 B::~B


  B::operator delete


 


  CATCH


    三个B::operator new的跟踪信息对应于a.b1a.b2a.b3的构造。其中,a.b1a.b2被完全构造(它们的构造函数都进入并退出了),而a.b3没有(它的构造函数只是进入了而没有退出)。注意这个:


3 B::B enter


 


  THROW


 


  B::operator delete


它表明,调用a.b3的构造函数,在其中抛出了异常,然后编译器自动释放了a.b3占用的内存。接下来的跟踪信息:


2 B::~B


  B::operator delete


1 B::~B


  B::operator delete


表明被完全构造的对象a.b2a.b1在释放其内存前先被析构了。


结论:所有完全构造的对象的析构函数被调用,所有对象的内存被释放。


 


1.3     Placement new


    8使用了“普通的”非Placement new语句来构造三个B对象。现在考虑这个变化:


// Example 9


 


// ... preamble unchanged


 


class B


   {


public:


   void *operator new(size_t const n, int)


      {


      std::cout << "  B::operator new(int)" << std::endl;


      return ::operator new(n);


      }


   // ... rest of class B unchanged


};


 


class A


   {


public:


   A() : b1(new(0) B(1)), b2(new(0) B(2)), b3(new(0) B(3))      {


      std::cout << "  A::A" << std::endl;


      }


   // ... rest of class A unchanged


   };


 


 


// ... main unchanged


    这个new语句


new(0) B(1)


有一个placement参数0。因为参数的类型是int,编译器需要operator new的一个接受额外int参数的重载版本。我已经增加了一个满足要求的B::operator new函数。这个函数实际上并不使用这个额外参数,此参数只是个占位符,用来区分 placement new还是非placement new 的。


    因为Visual C++ 5不完全支持 placement new placement delete,例9不能在其下编译。程序在Visual C++ 6下能编译,但在下面这行上生成了三个Level 4的警告:


A() : b1(new(0) B(1)), b2(new(0) B(2)), b3(new(0) B(3))


内容都是:


'void *B::operator new(unsigned int, int)':


   no matching operator delete found;


   memory will not be freed if initialization


   throws an exception


    想知道编译器为什么警告,运行程序,然后和例8比较输出:


  B::operator new(int)


1 B::B enter


1 B::B exit


  B::operator new(int)


2 B::B enter


2 B::B exit


  B::operator new(int)


3 B::B enter


 


  THROW


 


2 B::~B


  B::operator delete


1 B::~B


  B::operator delete


 


  CATCH


 


输出是相同的,只一个关键不同:


3 B::B enter


 


  THROW


和例8一样的是,a.b3的构造函数进入了并在其中抛出了异常;但和例8不同的是,a.b3的内存没有自动删除。我们应该留意编译器的警告的!


 


1.4     最后,Placement delete


    想要“自动删除”能工作,一个匹配抛异常的对象的operator newoperator delete的重载版本必须可用。摘自 C++标准 (subclause 5.3.4p19, "New")


    如果参数的数目相同并且除了第一个参数外其类型一致(在作了参数的自动类型转换后),一个placement的释放函数与一个placement的分配函数相匹配。所有的非palcement的释放函数匹配于一个非placement的分配函数。如果找且只找到一个匹配的释放函数,这个函数将被调用;否则,没有释放函数被调用。


    因此,对每个placement分配函数


   void operator new(size_t, P2, P3, ..., Pn);


都有一个对应的placement释放函数


   void *operator delete(void *, P2, P3, ..., Pn);


这里


   P2, P3, ..., Pn


一般是相同的参数队列。我说“一般”是因为,根据标准的说法,可以对参数进行一些转换。再引于标准(subclause 8.3.5p3, "Functions"),基于可读性稍作了修改:


    在提供了参数类型列表后,将对这些类型作一些转换以决定函数的类型:


l         所有参数类型的const/volatile描述符修饰将被删除。这些cv描述符修饰只影响形参在函数体中的定义,不影响函数本身的类型。


    例如:类型


    void (*)(const int)


变为


    void (*)(int)


l         如果一个存储类型描述符修饰了一个参数类型,此描述符被删除。这存储类型描述符修饰只影响形参在函数体中的定义,不影响函数本身的类型。


    例如:


register char *


变成


char *


 


转换后的参数类型列表才是函数的参数类型列表。


 


    顺便提一下,这个规则同样影响函数的重载判断,signaturesname mangling。基本上,函数参数上的cv描述符和存储类型描述符的出现不影响函数的身份。例如,这意味着下列所有申明引用的是同一个函数的定义。


l         void f(int)


l         void f(const int)


l         void f(register int)


l         void f(auto const volatile int)


 


    增加匹配于我们的placement operator newplacement operator delete函数:


// Example 10


 


// ... preamble unchanged


 


class B


   {


public:


    void operator delete(void *const p, int)


      {


      std::cout << "  B::operator delete(int)" << std::endl;


      ::operator delete(p);


      }


   // ... rest of class B unchanged


};


 


// ... class A and main unchanged


    然后重新编译并运行。输出是:


  B::operator new(int)


1 B::B enter


1 B::B exit


  B::operator new(int)


2 B::B enter


2 B::B exit


  B::operator new(int)


3 B::B enter


 


  THROW


 


  B::operator delete(int)


2 B::~B


  B::operator delete


1 B::~B


  B::operator delete


 


  CATCH


和例8非常相似,每个operator new匹配一个operator delete


    一个可能奇怪的地方:所有B对象通过placement operator new分配,但不是全部通过placement operator delete释放。记住,placement operator delete只(在plcaement operator new失败时)被调用于自动摧毁部分构造的对象。完全构造的对象将通过delete语句手工摧毁,而delete语句调用非placement operator delete。(WQ注:没有办法调用placement delete语句,只能调用plcaement operator delete函数,见9.2。)


 


1.5     光阴似箭


    在第九部分,我将展示placement delete是多么地灵巧(远超过现在展示的),但有小小的隐瞒和简化。并示范一个新的机制来在构造函数(如A::A)中更好地容忍异常。

C++异常处理示例

这两天我写了一个测试c++异常处理机制的例子,感觉有很好的示范作用,在此贴出来,给c++异常处理的初学者入门。本文后附有c++异常的知识普及,有兴趣者也可以看看。      下面的代码直接贴到你...
  • loveRooney
  • loveRooney
  • 2014年08月08日 14:49
  • 830

C++中异常处理中的异常重新抛出的一种用法

本文讨论了C++异常处理中重复抛出异常的一种用法
  • u010857719
  • u010857719
  • 2016年09月08日 21:56
  • 762

Android NDK之JNI异常处理

处理本机代码中的异常      为了处理以Java代码实现的方法执行中抛出的异常,或者是以本机代码编写的方法抛出的Java异常,JNI提供了Java异常机制的钩子程序。该机制与C/C++中常规函...
  • u013378580
  • u013378580
  • 2016年08月25日 16:24
  • 636

C++异常处理类与自定义异常处理类

转自:http://blog.csdn.net/makenothing/article/details/43273137 例1:自定义一个继承自excepton的异常类myExcep...
  • u014805066
  • u014805066
  • 2017年03月29日 17:32
  • 544

c++异常处理机制示例及讲解

原文链接:http://ticktick.blog.51cto.com/823160/191881 下面的代码直接贴到你的console工程中,可以运行调试看看效果,并分析c++的异常机制。 ...
  • u013727453
  • u013727453
  • 2015年04月24日 15:30
  • 1747

深入理解C++中的异常处理机制

异常处理 增强错误恢复能力是提高代码健壮性的最有力的途径之一,C语言中采用的错误处理方法被认为是紧耦合的,函数的使用者必须在非常靠近函数调用的地方编写错误处理代码,这样会使得其变得笨拙和难以使用。C...
  • u013982161
  • u013982161
  • 2016年11月06日 12:35
  • 826

【VS开发】C++异常处理操作

异常处理的基本思想是简化程序的错误代码,为程序键壮性提供一个标准检测机制。 也许我们已经使用过异常,但是你会是一种习惯吗,不要老是想着当我打开一个文件的时候才用异常判断一下,我知道对你来说你喜欢...
  • LG1259156776
  • LG1259156776
  • 2016年05月21日 21:34
  • 1669

基于TCP传输的网络编程异常处理

 基于TCP传输的网络编程异常处理 一:进程一端退出(exit,CTRL+C,挂掉)(跟主动CLOSE、主动关机一样)  内核会关闭所有句柄触发FIN分节发送(但如果设置了SO_LINGER...
  • doitsjz
  • doitsjz
  • 2017年03月11日 14:15
  • 624

linux C 异常处理的方式

目前遇到这样的问题,大概在2000多台服务器里面有100多多台一个c进程挂掉了,由于公司各种的流程调试起来非常困难。 这几天google了下找到了一些资料,捕获异常堆栈的,如http://spin....
  • wangyin159
  • wangyin159
  • 2015年07月23日 21:02
  • 604

C++/MFC全局未知异常捕获并进行调试

C++/MFC全局未知异常捕获Dump出来并进行调试全局捕获未知异常函数名: WINBASEAPI LPTOP_LEVEL_EXCEPTION_FILTER WINAPI Set...
  • KellyGod
  • KellyGod
  • 2017年04月01日 00:25
  • 1390
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C与C++中的异常处理8
举报原因:
原因补充:

(最多只允许输入30个字)