智能指针与异常处理

一、智能指针

1、常规指针的缺点

当一个常规指针离开了作用域时,只有该指针变量本身占用的内存空间(4/8)会被释放,而它指向的内存空间不会自动释放,当free/delete/delete[]语句忘记执行或者无法执行,形成内存泄露

(如何定位内存泄露、如何预防内存泄露)

2、智能指针的优点

智能指针是一个封装了常规指针的类类型对象,并且重载了*和->运算符,使用起来与常规指针相近

当智能指针离开作用域时,它的析构函数必定执行,从而在析构函数中执行释放常规指针的操作,这样就做到了自动释放的效果,从而避免内存泄露

智能指针是一个类模板

3、C++STL中的四种智能指针

auto_ptr、shared_ptr、unique_ptr、weak_ptr

在C++98标准中只有第一个auto_ptr,C++11只支持后三个,第一个被弃用,使用会产生警告,需要提供头文件 <memory>

①auto_ptr

采用独占拥有模式,不能同时有多个auto_ptr指向同一个内存,但是不能完全实现,有时候会指向同一个内存,有隐患

auto_ptr<int> p1(new int(123));
auto_ptr<int> p2;	//	可以悬空
p2 = p1;	//	允许,但p1转移所有权给p2,p1可能变成空指针
*p1;	//	可能段错误

注意:这种独占式不一定成立,p1是否转移给p2不确定

使用格式

1、auto_ptr<类型> 对象名(new 类型名);
2、类型* p = new 类型;
	auto_ptr<类型> 对象名(p);
②unique_ptr 独享指针

是auto_ptr的升级,完全实现独占式拥有模式,保证同一时间中只有一个unique_ptr指向某个内存

通过把拷贝构造、赋值操作函数声明为delete来实现不能给另一个unique_ptr对象赋值的效果

unique_ptr<int> p1(new int);
unique_ptr<int> p2;
p2 = p1;   //   报错
p2 = unique_ptr<int>(new int);
//  允许指向匿名unique_ptr对象的内存

可以通过C++的全局函数 move() 来转移内存的指向给另一个unique_ptr

p2 = move(p1);  // 让p2指向p1原来的内存,p1一定会变成空指针,p2在改变指向之前,会先释放自己原来的内存
③shared_ptr 共享指针

采用共享的拥有模式,可以允许多个shared_ptr指向相同内存

当一个内存被shared_ptr指向时,内部有一个引用计数器+1

当指向该内存的某个shared_ptr离开作用域或者改变指向或者通过reset()时,引用计数器会-1

当该内存的引用计数器被减为0时,由最后一个离开的shared_ptr在结束前释放该内存

成员函数:

函数功能
get()获取指向内存的地址编号
use_count()获取引用计数器的值
unique()判断指向的内存是否只有一个shared_ptr指向(0 不独占、1 独占)
reset()放弃对内存的指向 计数-1

全局函数:

函数功能
val2 = move(val1)移动val1的值和使用权给val2
swap()交换两个对象

sharead_ptr的循环引用问题:

当两个类(A B)中都有可以指向对方类型的shared_ptr智能指针成员变量(pB pA),并且在类外通过shared_ptr指向new出来的两个类对象(pa指向类A pb指向类B),并且让他们的成员变量pB pA 指向对方,此时构成循环引用,导致就算类对象pa pb销毁,但它们指向的对象的引用计数无法减为0(pA pB还在指向),导致无法释放对象内存,内存泄漏

④weak_ptr 弱引用指针

weak_ptr是为了配合shared_ptr而引入的一种智能指针,它指向一个由shared_ptr管理的对象而不影响该对象的生命周期,也即是说将一个weak_ptr指向一个shared_ptr对象,或者离开时都不会改变该对象的引用计数,只有当最后一个shared_ptr离开该对象才会销毁

weak_ptr更像是shared_ptr的一个助手,而不是独立的智能指针

因此当发生shared_ptr的循环引用产生死锁时,可以把其中一个类的shared_ptr成员变量改为weak_ptr,即可避免产生死锁

二、异常处理

程序的错误大致分三种:

语法错误、逻辑错误、运行时错误

运行时错误发生在程序运行期间发生的问题:除零、内存分配失败、非法访问内存、文件不存在、数组越界

C++的异常处理机制就是为了解决运行时错误而引用的

C语言中运行时错误如果不管,系统会执行默认操作,可能会让程序终止产生程序崩溃,也可能不终止,但是运行结果不正确

C++提供的异常处理机制,能够捕捉到运行时错误,至少提供了告诉调用者发生了什么事情导致终止的方式,然后再终止

三、如何抛出异常

throw 数据;
//	数据可以是任意类型
//	不要抛出局部变量的地址,因为该地址有可能被释放
//	抛出的数据不是直接传递给捕获的变量,而是先创建一个匿名对象存储该数据,然后传递该匿名对象

四、如何捕获异常

try{
    //	可能会产生异常的代码或函数调用
}catch(类型1& 变量名){
    //	如果使用 类型 变量名 方式捕获异常,会对匿名对象再拷贝一次,浪费资源时间,所以一般使用引用获取该匿名对象,可以减少一次拷贝
    //	进行异常处理
}catch(类型2& 变量名){
    
}...

五、异常说明(异常规范)

返回值 函数名(形参列表)[异常说明throw(类型名1,类型名2)]
{
    
}

例如:

void func(void)throw(类型1,类型2)
void func(void)throw	//	表示不抛异常

异常说明:相当于该函数的限制或承诺,只抛出说明过异常类型,如果抛出说明外的类型,可以抛出,但是不能接住

但是不同编译器对异常说明的实现不同,有的听从,有的不听从

异常说明是C++98一项功能,C++11后就抛弃了,不建议使用

C++11中使用

void func(int x) noexcept

六、标准异常

C++已经定义好的异常类,当对应的异常发生时,会自动地抛出对应地异常类对象

std::exception 所有标准异常类的父类,能够捕获所有的标准异常

std::bad_alloc new分配内存失败时抛出的异常,std::bad_array_new_length 是它的子类 new分配内存数量有误会抛出

std::bad_cast 该异常可以通过 dynamic_cast 抛出

需要#include <typeinfo>

std::bad_typeid 该异常可以通过typeid 抛出

当获取具有多态属性的类型指针解引用的类型时,如果不能确定解引用后是哪个类型时,会抛出该异常

Base* b = new Base;
Base* b1 = new Son;
Base* b2 = NULL;
typeid(*b);    //类型 Base
typeid(*b1);   //类型 Son
typeid(*b2);   //抛异常

七、自定义异常类

通过设计一个继承了exception的异常类,可以个性化地抛出想要的异常

#define ERROR(...) Error(__TIME__,__FILE__,__func__,__LINE__,__VA_ARGS__)

class Error : public exception
{
	string time;
	string file;
	string func;
	size_t line;
	string error;
public:
	Error(const string& time,const string& file,const string& func,size_t line,const string& error):time(time),file(file),func(func),line(line),error(error){}
	~Error(void){}

	const string& what(void)
    {
		return error;
	}

	friend ostream& operator<<(ostream& os,const Error& err)
    {
        return os << "time:" << err.time << " file:" << err.file <<" func:" << err.func << " line:" << err.line << " error:" << err.error;
    }
};

八、使用异常需要注意的问题

①不要抛出局部变量、对象的地址,而是抛出变量、对象本身

②建议使用引用的方式来捕获异常,减少一次拷贝

③不要在构造函数、析构函数中抛异常

④在捕获异常时,先捕获子类类型,再捕获父类类型

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值