C++11的特性:智能指针

一、使用智能指针的原因

某些情况下,申请了空间,new或者malloc了,但是可能没有释放该空间。
例如,在异常捕获中,先调用了某个函数,这个函数中途发生了错误且申请了一块空间,虽然我们捕获了异常,但没有将这个函数申请的空间释放。

try {
	Func();
} catch (exception& e) {
	...
}

例如,这里的 Func() 函数申请了一块地址空间后报错了,我们虽然捕获了该异常,却没有释放 Func() 申请的空间。由此发生内存泄漏。在长期不关闭的服务器上,内存泄漏是很严重的问题。

二、智能指针的设计思想

简而言之就是,设计一个模板类,将原本的指针传给这个类,在析构函数中将它自动释放,因为出了一个类的作用域,会自动调用析构函数。通过这个类对象生命周期来控制资源的释放。即,把管理一份资源的责任托管给了一个对象。
另外,还重载了运算符 * 和 -> 。

template<class T>
class smart_ptr {
public:
    smart_ptr(T* ptr = nullptr) // 构造函数中赋值
        : _ptr(ptr) {}
        
    ~smart_ptr() {// 析构函数中自动释放
        if(_ptr)
            delete _ptr;
            
	T& operator*() {
		return *_ptr;
	}
	
	T* operator->() {
		return _ptr;
	}
}
private:
    T* _ptr;
};

smart_ptr<int> sp(new int); // 使用该智能指针类

三、一种不再使用的智能指针:auto_ptr

当出现拷贝构造时,由于是浅拷贝,会使得两个智能指针指向同一块地址空间,生命周期结束时,还会调用两次析构函数,相当于释放了两次同一块地址空间。
因此,曾经的智能指针 auto_ptr 使用了一种方法:管理权转移。

auto_ptr(auto_ptr<T>& sp)
	:_ptr(sp._ptr) {
	sp._ptr = nullptr; // 将原本的指针置空
}

由于原本的释放,会判断是否为空,如果为空就不释放。所以这里不会产生释放两次的情况。
但又有了新的问题,原本的智能指针不能再使用了(已经变为空指针了)。

四、C++11中的智能指针一:unique_ptr

头文件:

#include <memory>

unique_ptr 的原理是,不允许拷贝:

unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;

详细版本是:

template<class T>
class unique_ptr {
public:
    unique_ptr(T* ptr = nullptr) // 构造函数中赋值
        : _ptr(ptr) {}
    ~unique_ptr() {// 析构函数中自动释放
        if(_ptr) delete _ptr;
            
	T& operator*() { return *_ptr; }
	T* operator->() { return _ptr; }
	unique_ptr(const unique_ptr<T>& sp) = delete;
	unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
}
private:
    T* _ptr;
};

unique_ptr<int> sp(new int); // 使用该智能指针类

五、C++11中的智能指针二:shared_ptr

5.1 原理

shared_ptr 的原理是,使用引用计数来判断,是否该释放。
当引用计数不为 0 时,说明除了自己还有其他对象在使用该份资源,不需要释放,只需要将引用计数 -1;当引用计数为 0 时,说明自己是最后一个使用该资源的对象,才真正的释放。

具体方法:第一个对象,在堆上申请一块空间,来维护该对象的引用计数(使用指针的方式来维护,方便后面浅拷贝时,能通过指针改变引用计数的值):

private:
    T* _ptr;
    int* _pRefCount; // 维护该对象的引用计数

在构造函数中,初始化为 1 :

shared_ptr(T* ptr = nullptr)
            :_ptr(ptr)
            , _pRefCount(new int(1)) {}

后面每一个拷贝对象,都将原本的引用计数加 1 :

shared_ptr(const shared_ptr<T>& sp)
            :_ptr(sp._ptr)
            , _pRefCount(sp._pRefCount) {
	++(*_pRefCount);
}

析构时,将引用计数减 1 ,当引用计数为 0 时,释放该资源:

~shared_ptr() {
	if (--(*_pRefCount) == 0 && _ptr) {
        delete _ptr;
        delete _pRefCount;
	}
}

5.2 赋值操作

在赋值操作时会产生问题。

shared_ptr<int> sp1(new int);
shared_ptr<int> sp2(new int);
sp1 = sp2;

在上面这种情况下,应将 sp1 的引用计数 -1(若变0,还需释放),将 sp2 的引用计数 +1 。如下:

shared_ptr<T>& operator=(const shared_ptr<T>& sp) {
	// 如果不是自己赋值给自己
    if (_ptr != sp._ptr) { //同一资源的对象赋值操作,也可以判断出来
    	if (--(*_pRefCount) == 0 && _ptr) { // 需要释放原资源的情况
            delete _ptr;
            delete _pRefCount;
		}
		// 赋值
    	_ptr = sp._ptr;
        _pRefCount = sp._pRefCount;
        ++(*_pRefCount); // 已经是新的资源的引用计数了
	}
    return *this;
}

5.3 线程安全

但是目前,shared_ptr 不是线程安全的。如果两个线程同时使得引用计数 +1 或 -1,可能会产生问题。在 std 的 shared_ptr 中,还对此问题进行了解决。

5.4 使用

头文件:

#include <memory>

构造:

std::shared_ptr<int> p1;             //不传入任何实参
std::shared_ptr<int> p2(nullptr);    //传入空指针 nullptr
std::shared_ptr<int> p3(new int(10));
std::shared_ptr<int> p3 = std::make_shared<int>(10);
//调用拷贝构造函数
std::shared_ptr<int> p4(p3);//或者 std::shared_ptr<int> p4 = p3;
//调用移动构造函数
std::shared_ptr<int> p5(std::move(p4)); //或者 std::shared_ptr<int> p5 = std::move(p4);

注意,同一普通指针不能同时为多个 shared_ptr 对象赋值,否则会导致程序发生异常。例如:

int* ptr = new int;
std::shared_ptr<int> p1(ptr);
std::shared_ptr<int> p2(ptr);//错误
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值