分享
 
 
 

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

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

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

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

1.1 自动删除

要实际验证它,在上次的例6中增加带跟踪信息的operator new和operator 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 new和operator 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 new和operator delete。我们需要一些方法来隔离出我们感兴趣的调用过程,并只输出它们的跟踪信息。

1.2 类属new和delete

C++又救了我们。不用跟踪全局的operator new和operator delete,我们可以跟踪其类属版本。既然我们感兴趣的是B对象的分配和释放过程,我们只需将operator new和operator 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 new和operator delete,可以控制如何及在哪儿分配这些对象。

对我们的例子,特殊的堆管理是没必要的。因此,我在类属operator new 和operator delete中调用了其全局版本而不再是malloc和free,并去除了对头文件<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.b1、a.b2和a.b3的构造。其中,a.b1和a.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.b2和a.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 new的operator 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 *

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

顺便提一下,这个规则同样影响函数的重载判断,signatures和name 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 new的placement 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)中更好地容忍异常。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有