c++ primer 随笔(6) 用于大型程序的工具

异常处理

使用throw抛出异常,使用try catch块捕获并处理异常。
异常是通过抛出对象而引发的,该对象的类型决定应该激活哪个处理代码,被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那个。
异常以类似将实参传入函数的方式抛出和捕获,这要求抛出的异常类型必须是可复制的,若抛出的类型是一个数组或函数,被抛出的对象将自动转化为一个指向数组首地址或函数的指针(事实上函数实参传参也是这样做的)。
执行throw的时候,不会执行跟在throw后面的语句,而是将控制从throw转移到匹配的catch,这将导致两个重要结果:
1.沿着调用链的函数提早退出
2.一般而言,在处理异常时,抛出异常的块中的局部存储已经不存在了
通常来说,抛出指针是一个坏主意,因为这要求在存在处理代码的任意地方都存在指针所指向的对象,抛出一个指向局部对象的指针会导致内存泄漏的问题。
抛出异常并处理的控制转移机制我们称之为“栈展开”。

1.为局部对象调用析构函数
如果一个块通过new动态分配内存,而该块因某些异常而退出,编译器将不会释放这块内存,因而我们需要手动释放内存。
2.析构函数不应该抛出异常
在为某个异常进行栈展开的时候,析构函数如果又抛出一个自己的未经处理的异常,将会导致调用标准库terminate函数,一般来说,terminate函数将调用abort函数,强制从整个程序非正常退出,这将导致析构函数不能正常运行释放空间。
3.异常与构造函数
与析构函数不同的是,构造函数常常抛出异常,抛出异常将导致该对象部分被构造,应当注意的是,抛出异常时,应保证那些被部分构造的对象能被撤销。
4.对于未捕获的异常
被抛出而未被捕获的异常将导致程序调用库函数terminate进行处理。
5.异常与继承
派生类型到基类类型的转换是合法的,因此基类的异常说明符可用于捕获派生类的异常对象,若被抛出的异常对象是派生类类型的,而捕获语句是基类类型的,那么catch将不能使用派生类特有的任何成员。

带有因继承而相关的类型的多个catch子句,必须从最低派生类到最高派生类排序

有时单个catch语句不能完全处理异常,在进行了一些矫正行动后,可能需要调用函数调用链中更上层的函数来处理,catch可以通过重新抛出将异常传递给调用链中更上层的函数。
重新抛出的格式是后面不跟类型或表达式的一个单独的throw

捕获所有异常的处理代码:
catch(…){

}
上面的语句可以捕获所有的异常,因此常用于最下方的catch块,以避免程序捕获不到可能出现的异常。

RAII(常用)

c++的内存资源是手动管理的,因此为了内存在使用后被及时释放,我们常用被称为RAII的机制(资源获取即初始化),具体实现就是:
在构造函数中动态分配内存,实现对内存资源的管理。
而在析构函数中释放内存。
这样编译器将在对象超出生存期时自动调用析构函数时将内存释放掉,以达到内存的合理管理。
此外,使用这样的机制,将还带来一个优点,一般情况下,在程序抛出一个异常时,如果此时内存还没有释放,该内存将永远不会释放。而采用RAII的方式,若程序抛出一个异常,程序仍能将该内存释放。

auto_ptr

标准库的auto_ptr类是一个异常安全的智能指针
应当注意的是:auto_ptr对象只能保存一个指向对象的指针,而不能用于指向动态分配的数组,这将导致一个未定义的运行时行为。
ap1=ap2表示将所有权从ap2转给ap1,删除ap1指向的对象并且使ap1指向ap2指向的对象,使ap2成为未绑定的。
同样的,auto_ptr <T> ap1(ap2)初始化也是一样的含义。
而auto_ptr <T> ap(c)的含义则是让auto_ptr形式的指针初始化指向对象c;
普通的赋值将用ap.reset(p)来完成,即删除旧的ap指向的对象并将ap绑定到p。
ap返回对ap所绑定的对象的引用
ap->返回ap保存的指针
auto_ptr被复制或赋值时,有不寻常的行为,因此不能将其储存在标准库容器类型中。

1.为异常安全的内存分配使用auto_ptr

如果通过常规指针分配内存,而程序在执行delete操作之前抛出异常,程序将不会释放该内存,该内存块不会被回收。而使用auto_ptr类型的指针,即使程序异常退出,该内存也能被释放。

2.auto_ptr是可以保存任何类型指针的模板

示例:
auto_ptr<string> ap1(new string(“HelloWorld”));
给auto_ptr类型的指针绑定一个由new表达式返回的对象是最常见的方式。

3.auto_ptr对象的复制和赋值是破坏性操作

应当注意的是,这是auto_ptr与普通指针最大的不同:
普通指针的复制与赋值,操作后两个指针将指向同一对象。
而对于auto_ptr类型的指针来说,复制与赋值操作是将基础对象的所有权转交给副本,原来的auto_ptr将被重置为未绑定状态。因此,这要求复制或赋值操作的左右操作数都必须是可修改的左值。
同时还应注意的是,若左auto_ptr指向某个对象,复制与赋值操作将首先解绑该指针与其绑定的对象。

4.auto_ptr的默认构造函数与测试

若不给定初识式,auto_ptr对象是未绑定的,它不指向任何对象,其内部指针值置为NULL,对其解引用,将会导致一个内存错误。
而auto_ptr的成员函数get()则表明它是否绑定了一个对象。
作为判断条件时:
if(ap1.get())
{

}

5.auto_ptr的reset操作

auto_ptr对象与内置指针的另一个区别是:不能将一个地址(或其他指针)赋给auto_ptr对象
而必须要调用reset函数来改变指针

6.auto_ptr的一些陷阱

(1) 不要使用auto_ptr对象保存指向静态分配内存的指针,否则在auto_ptr的指针被撤销时,将释放一个静态分配内存的指针,导致未定义的行为。
(2) 永远不要使用两个auto_ptr对象指向同一个对象
(3) 不要使用auto_ptr对象保存动态分配内存数组的指针,因为当auto_ptr对象被删除时,它使用delete而不是delete[ ]来释放数组内存。
(4) 不要将auto_ptr对象存储在容器中。

命名空间

命名空间为防止名字冲突提供了一种可控的机制,命名空间能划分全局命名空间。

声明using namespace mynsc;
表示将命名空间mynsc中的变量声明为全局命名空间的,使用其成员时,不必再加 : : 操作符,这带来一定程度的便利,但常用这样的声明并不是一种好的编程习惯,通常除了命名空间std,其余的都不加using声明。
另一种声明方式则每次只引入一个命名空间成员。
using namespace mynsc : : a ;
命名空间可以是不连续的,这意味着,命名空间可以在几个部分中定义,命名空间的总体由它分离定义的总和构成,命名空间是累积的。

多重继承,多继承与虚继承

多继承是从多个基类中继承,多重继承是多层次继承
对于多继承的派生类,它的指针或引用可以转换为其任意基类的指针或引用,调用函数时,常常会遇到二义性冲突的问题。
虚继承应当用于一个派生类的基类又是来自同一个基类的派生类的情况下,否则会导致基类的构造函数被重复调用。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值