分享
 
 
 

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

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

1. 部分构造及placement delete

讨论在一般情况下的部分构造、动态生成对象时的部分构造,以及用 placement delete来解决部分构造问题。

C++标准要求标准运行库头文件<new>提供几个operator delete的重载形式。在这些重载形式中,Visual C++ 6缺少:

l void operator delete(void *, void *)

而Visual C++ 5缺少:

l void operator delete(void *, void *)

l void operator delete(void *, std::nothrow_t const &)

这些重载形式支持placement delete表达式,并解决了一个特殊问题:释放部分构造的对象。在这次和接下来一次,我将给出一般情况下的部分构造、动态生成对象时的部分构造,以及用 placement delete来解决部分构造问题的例子。

1.1 部分构造

看这个例子:

// Example 1

#include <iostream>

class A

{

public:

A()

{

throw 0;

}

};

int main()

{

try

{

A a;

}

catch(...)

{

std::cout <<"caught exception" << std::endl;

}

return 0;

}

因为A的构造函数抛出了一个异常,a对象没有完全构造。在这个例子中,没有构造函数有可见作用:因为A没有子对象,构造函数实际上没有任何操作。但,考虑这样的变化:

// Example 2

#include <iostream>

class B

{

public:

B()

{

throw 0;

}

};

class A

{

private:

B const b;

};

// ... main same as before ...

现在,A的构造函数不是无行为的,因为它构造了一个B成员对象,而它里面会抛异常。程序对这个异常作出什么反应?

从C++标准中摘下了四条(稍作了简化)原则:

l 一个对象被完全构造,当且仅当它的构造函数已经完全执行,而它的析构函数还没开始执行。

l 如果一个对象包含子对象,包容对象的构造函数只有在所有子对象被完全构造后才开始执行。

l 一个对象被析构,当且仅当它被完全构造。

l 对象按它们被构造的反序进行析构。

因为抛出了一个异常,B::B没有被完全执行。因此,B的对象A::b既没有被完全构造也没有被析构。

要证明这点,跟踪相应的类成员:

// Example 3

#include <iostream>

class B

{

public:

B()

{

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

throw 0;

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

}

~B()

{

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

}

};

class A

{

public:

A()

{

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

}

~A()

{

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

}

private:

B const b;

};

// ... main same as before ...

当运行时,程序将只输出

B::B enter

caught exception

从而显示出对象a和b既没有完全构造也没有析构。

1.2 多对象

使例子变得更有趣和更有说明力,把它改得允许部分(不是全部)对象被完全构造:

// Example 4

#include <iostream>

class B

{

public:

B(int const ID) : ID_(ID)

{

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

if (ID_ > 2)

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(1), b2(2), b3(3)

{

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

}

~A()

{

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

}

private:

B const b1;

B const b2;

B const b3;

};

// ... main same asbefore ...

注意B的构造函数现在接受一个对象ID值的参数。用它作B的对象的唯一标记并决定对象是否完全构造。大部分跟踪信息以这些ID开头,显示为:

1 B::B enter

1 B::B exit

2 B::B enter

2 B::B exit

3 B::B enter

2 B::~B

1 B::~B

caught exception

b1和b2完全构造而b3没有。所以,b1和b2被析构而b3没有。此外,b1和b2的析构按其构造的反序进行。最后,因为一个子对象(b3)没有完全构造,包容对象a也没有完全构造和析构。

1.3 动态分配对象

将类A改为其成员变量是动态生成的:

// Example 5

#include <iostream>

// ... class B same as before ...

class A

{

public:

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

{

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

}

~A()

{

delete b1;

delete b2;

delete b3;

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

}

private:

B * const b1;

B * const b2;

B * const b3;

};

// ... main same as before ...

这个形式符合C++习惯用法:在包容对象的构造函数里分配成员变量,并对其填充数据,然后在包容对象的析构函数里释放它们。

编译并运行例5。输出是:

1 B::B enter

1 B::B exit

2 B::B enter

2 B::B exit

3 B::B enter

caught exception

其结果与例4相似,但有一个巨大的不同:因为~A没有被执行,其中的delete语句也就没有执行,被成功分配的*b1和*b2的析构函数也没有调用。例四中的不妙状况(三个对象析构了两个)现在更差了(三个对象一个都没有析构)。

实际上,没有比这更坏的了。记住,delete b1语句有两个作用:

l 调用*b1的析构函数~b。

l 调用operator delete释放*b1所占有的内存。

所以我们不光是遇到~B没有被调用所导致的问题,还有每个B对象造成的内存泄漏问题。这不是件好事。

B对象是A私有的,它们是实现细节,对程序的其它部分是不可见的。用动态生成B的子对象来代替自动生成B的子对象不该改变程序的外在行为,这表明了我们的例子在设计上的缺陷。

1.4 析构动态生成的对象

为了最接近例4的行为,我们需要在任何情况强迫delete语句的执行。将它们放入~A明显不起作用。我们需要找个能起作用的地方,我们知道它能被执行的地方。跳入脑海的解决方法中,最优雅的方法来自于C++标准运行库:

// Example 6

#include <iostream>

#include <memory>

// ... class B same as before ...

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;

};

// ... main same as before ...

auot_ptr读作“auto-pointer”。如名所示,auoto-pointer表现为通常的指针和自动对象的混合体。

std::auto_ptr是在<memory>中申明的类模板。一个std::auto_ptr<B>类型的对象的表现非常象一个通常的B*类型对象,关键的不同是:auto_ptr是一个实实在在的类对象,它有析构函数,而这个析构函数将在B*所指对象上调用delete。最终结果是:动态生成的B对象如同是个自动B对象一样被析构。

可以把一个auto_ptr<B>对象当作对动态生成的B对象的简单包装。在包装消失(析构)时,它也将被包装对象带走了。要实际看这个魔术戏法,编译并运行例6。结果是:

1 B::B enter

1 B::B exit

2 B::B enter

2 B::B exit

3 B::B enter

2 B::~B

1 B::~B

caught exception

Bingo!输出和例4相同。

你可能会奇怪为什么没有为b3调用~B。这表明了auto_ptr包装上的失败?根本不是。我们所读过的规则还在起作用。对b3进行的构造函数的调用接受了new B(3)传过来的参数。于是发生了一个异常终止了b3的构造。因为b3没有完全构造,它同样不会析构。

藏在atuo-pointer后面的想法没有新的地方;string对象实际上就是char数组的auto-pointer型包装。虽然如此,我仍然期望有一天我能更详细的讨论auto_ptr及其家族,目前只要把auto_ptr当作一个保证发生异常时能析构动态生成的对象的简单方法。

1.5 预告

既然b3的析构函数没有被调用,也就没有为其内存调用delete。如前面所见,被包装的B对象受到两个影响:

l 析构函数~B没有被调用。这是意料中的甚至是期望中的,因为B对象在先前没有完全构造。

l 内存没有被通过operator delete释放。不管是不是意料中的,它绝不是期望中的,因为B对象所占用的内存被分配了,即使B对象没有在此内存中完全构造。

我需要operator delete被调用,即使~B没有被调用。要实现这点,编译器必须在脱离delete语句的情况下调用operator delete。因为我知道b3是我的例子中的讨厌对象,我可以显式地为b3的内存调用operator delete;但要知道这只是教学程序,通常情况下我们不能预知哪个构造函数将失败。

不,我们所需要的是编译器检测到动态生成对象时的构造函数失败时隐含调用operator delete来释放对象占用的内存。这有些效仿编译器在自动对象构造失败时的行为:对象的内存如同程序体中的无用单元一样,是可回收的。

幸好,它有个大喜结局。要看这个结局,需到下回。在下回结束时,我将揭示C++语言如何提供了这个完美特性,为什么标准运行库申明了placement operator delete,以及为什么你可能想在自己的库或类中做同样的事。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
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- 王朝網路 版權所有