ITPub博客

首页 > 应用开发 > IT综合 > C与C++中的异常处理9 (转)

C与C++中的异常处理9 (转)

原创 IT综合 作者:worldblog 时间:2007-12-12 15:13:29 0 删除 编辑
C与C++中的异常处理9 (转)[@more@]

1.  placement new 和placement delete,及处理构造函数抛出的异常XML:namespace prefix = o ns = "urn:schemas-microsoft-com:Office:office" />

  当被调用了来清理部分构造时,operator delete的第一个void *参数带的是对象的地址(刚刚由对应的operator new返回的)。operator delete的所有额外placement参数都和传给operator new的相应参数的值相匹配。

  在代码里,语句

p = new(n1, n2, n3) T(c1, c2, c3);

的效果是

p = operator new(sizeof(T), n1, n2, n3);

T(p, c1, c2, c3);

 

  如果T(p, c1, c2, c3)构造函数抛出了一个异常,程序暗中调用

operator delete(p, n1, n2, n3);

  原则:当释放一个部分构造的对象时,operator delete从原始的new语句知道上下文。

 

1.1  Placement operator delete的参数

  要证明这点,增强我们的例子来跟踪相应的参数值:

// Example 11

 

#include

#include

 

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;

  }

  //

  // non-placement

  //

  void *operator new(size_t const n)

  {

  void *const p = ::operator new(n);

  std::cout << "  B::operator new(" << n <<

  ") => " << p << std::endl;

  return p;

  }

  void operator delete(void *const p)

  {

  std::cout << "  B::operator delete(" << p <<

  ")" << std::endl;

  ::operator delete(p);

  }

  //

  // placement

  //

  void *operator new(size_t const n, int const i)

  {

  void *const p = ::operator new(n);

  std::cout << "  B::operator new(" << n <<

  ", " << i << ") => " << p << std::endl;

  return p;

  }

  void operator delete(void *const p, int const i)

  {

  std::cout << "  B::operator delete(" << p <<

  ", " << i << ")" << std::endl;

  ::operator delete(p);

  }

private:

  int const ID_;

  };

 

class A

  {

public:

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

  {

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

  }

  ~A()

  {

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

  }

private:

  std::auto_ptr const b1;

  std::auto_ptr const b2;

  std::auto_ptr const b3;

  };

 

int main()

  {

  try

  {

  A a;

  }

  catch(...)

  {

   std::cout << std::endl;

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

  std::cout << std::endl;

  }

  return 0;

  }

  用Visual C++ 6编译并运行。在我的机器上的输出是:

  B::operator new(4, 11) => 007E0490

1 B::B enter

1 B::B exit

  B::operator new(4, 22) => 007E0030

2 B::B enter

2 B::B exit

  B::operator new(4, 33) => 007E0220

3 B::B enter

 

  THROW

 

  B::operator delete(007E0220, 33)

2 B::~B

  B::operator delete(007E0030)

1 B::~B

  B::operator delete(007E0490)

 

  CATCH

 

  注意这些数字:

l  4是每个被分配的B对象的大小的字节数。这个值在不同的C++实现下差异很大。

l  如007E0490这样的值是operator new返回的对象的地址,作为this指针传给T的成员函数的,并作为void *型指针传给operator delete。你看到的值几乎肯定和我的不一样。

l  11,22和33是最初传给operator new的额外placement参数,并在部分构造时传给相应的placement operator delete。

 

1.2  手工调用operator delete

  所有这些operator new和operator delete的自动匹配是很方便的,但它只在部分构造时发生。对通常的完全构造,operator delete不是被自动调用的,而是通过明确的delete语句间接调用的:

p = new(1) B(2); // calls operator new(size_t, int)

// ...

delete p;  // calls operator delete(void *)

  这样的顺序其结果是调用placement operator new和非placement operator delete,即使你有对应的(placement)operator delete可用。

  虽然你很期望,但你不能用这个方法强迫编译器调用placement operator delete:

delete(1) p; // error

而必须手工写下delete语句将要做的事:

p->~B();  // call *p's destructor

B::operator delete(p, 1); // call placement

  //  operator delete(void *, int)

  要和自动调用operator delete时的行为保持完全一致,你必须保存通过new语句传给operator new的参数,并将它们手工传给operator delete。

p = new(n1, n2, n3) B;

// ...

p->~B();

B::operator delete(p, n1, n2, n3);

 

1.3  其它非placement delete

  贯穿整个这个专题,我说了operator new和operator delete分类如下:

函数对

l  void *operator new(size_t)

l  void operator delete(void *)

是非placement分配和释放函数。

所有如下形式的函数对

l  void *operator new(size_t, P1, ..., Pn)

l  void operator delete(void *, P1, ..., Pn)

是placement分配和释放函数。

  我这样说是因为简洁,但我现在必须承认撒了个小谎:

void operator delete(void *, size_t)

也可以是一个非placement释放函数而匹配于

void *operator new(size_t)

虽然它有一个额外参数。如你所猜想,operator delete的size_t参数带的是传给operator new的size_t的值。和其它额外参数不同,它是提供完全构造的对象用的。

  在我们的例子中,将这个size_t参数加到非placement operator delete上:

// Example 12

 

// ... preamble unchanged

 

class B

  {

  void operator delete(void * const p, size_t const n)

  {

  std::cout << "  B::operator delete(" << p <<

  ", " << n << ")" << std::endl;

  ::operator delete(p);

  }

  // ... rest of class B unchanged

  };

 

// ... class A and main unchanged

The results:

 

  B::operator new(4, 11) => 007E0490

1 B::B enter

1 B::B exit

  B::operator new(4, 22) => 007E0030

2 B::B enter

2 B::B exit

  B::operator new(4, 33) => 007E0220

3 B::B enter

 

  THROW

 

  B::operator delete(007E0220, 33)

2 B::~B

  B::operator delete(007E0030, 4)

1 B::~B

  B::operator delete(007E0490, 4)

 

  CATCH

  注意,为完全构造的对象,将额外的参数4提供给了operator delete。

 

1.4  显而易见的矛盾

  你可能奇怪:C++标准允许非placement operator delete自动知道一个对象的大小,却否定了placement operator delete可具有相同的能力。要想使它们保持一致,一个placement分配函数

void *operator new(size_t, P1, P2, P3)

应该匹配于这样一个placement释放函数

void operator delete(void *, size_t, P1, P2, P3)

  但事实不是这样,这两个函数不匹配。为什么语言被这样设计?我猜有两个原因:效率和清晰。

  大部分情况下,operator delete不需要知道一个对象的大小;强迫函数任何时候都接受大小参数是低效的。并且,如果标准允许size_t参数可选,这样的含糊将造成:

void operator delete(void *, size_t, int)

在不同的环境下有不同的意义,决定它将匹配哪个:

void *operator new(size_t, int)

还是

void *operator new(size_t, size_t, int)

  如果因下面的语句抛了个异常而被调用:

p = new(1) T; // calls operator new(size_t, int)

operator delete的size_t参数将是sizeof(T);但如果是被调用时是

p = new(1, 2) T; // calls operator new(size_t, size_t, int)

operator delete的size_t参数将是new语句的第一个参数值(这里是1)。于是,operator delete将不知道怎么解释它的size_t值。

  我估计,你可能想知道是否非placement的函数

void operator delete(void *, size_t)

同时作为一个placement函数匹配于

void *operator new(size_t, size_t)

  如果它被允许,operator delete将遇到前面讲的同样问题。而不被允许的话, C++标准将需要其规则的一个例外。

  我没发现规则的这样一个例外。我试过几个编译器,— including EDG’s front end, my expert witness on such matters — 并认为:

void operator delete(void *, size_t)

实际上能同时作为一个placement释放函数和一个非placement释放函数。这是个重要的提醒。

  如果你怀疑我,就将例12的placement operator delete移掉。

// Example 13

 

// ... preamble unchanged

 

class B

  {

//  void operator delete(void *const p, int const i)

//  {

//  std::cout << "  B::operator delete(" << p <<

//  ", " << i << ")" << std::endl;

//   ::operator delete(p);>

//  }

  // ... rest of class B unchanged

  };

 

// ... class A and main unchanged

 

  现在,类里有一个operator delete匹配于两个operator new。其输出结果和例12仍然相同。(WQ注:结论是正确的,但不同的编译器下对例12到例14的反应相差很大,很是有趣!)

 

1.5  结束

  两个最终要点:

l  贯穿我整个对::operator new和B::operator delete的讨论,我总是将函数申明为非static。通常这样的申明意味着有this指针存在,但这些函数的行为象它们没有this指针。实际上,在这些函数来试图引用this,你将发现代码不能编译。不象其它成员函数,operator new和operator delete始终是static的,即使你没有用static关键字。

l  无论我在哪儿提到operator new和operator delete,你都可以用operator new[] 和operator delete[]代替。相同的模式,相同的规则,和相同的观察结果。(虽然Visual C++标准运行库的中缺少operator new[]和operator delete[],编译器仍然允许你定义自己的数组版本。)

l

  我想,这个结束了我对plcement new和delete及它们在处理构造函数抛出的异常时扮演的角色的解释。下次,我将介绍给你一个不同的技巧来容忍构造函数抛出的异常。


来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/10752043/viewspace-992214/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论