# 1.     placement new 和placement delete，及处理构造函数抛出的异常

当被调用了来清理部分构造时，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 <iostream>

#include <memory>

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<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;

}

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         112233是最初传给operator new的额外placement参数，并在部分构造时传给相应的placement operator delete

## 1.2     手工调用operator delete

所有这些operator newoperator 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，即使你有对应的（placementoperator delete可用。

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

delete(1) p; // error

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 newoperator delete分类如下：

l         void *operator new(size_t)

l         void operator delete(void *)

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

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

placement分配和释放函数。

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

void operator delete(void *, size_t)

void *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)

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 deletesize_t参数将是sizeof(T)；但如果是被调用时是

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

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

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

void operator delete(void *, size_t)

void *operator new(size_t, size_t)

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

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

void operator delete(void *, size_t)

如果你怀疑我，就将例12placement 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 newB::operator delete的讨论，我总是将函数申明为非static。通常这样的申明意味着有this指针存在，但这些函数的行为象它们没有this指针。实际上，在这些函数来试图引用this，你将发现代码不能编译。不象其它成员函数，operator newoperator delete始终是static的，即使你没有用static关键字。

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

l

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

0 0