本篇博客,我将对为什么引入智能指针?智能指针的设计原理以及作用是什么?常用的智能指针有哪些?它是如何实现的来进行浅谈!
一、智能指针的引入
我们先来看一个例子(如下图):
于是,师徒二人展开激烈的讨论:
此时我们就知道了为什么会有智能指针的存在。
二、智能指针的实现原理
1.概念:
- 智能指针是一个类,它使用了RAII技术,在该类的构造函数中传入一个普通指针,析构函数中释放传入的指针。
2.几点说明:
(1)RAII(Resource Acquisition Is Initialization)–资源分配即初始化,RAII是指:定义一个类来封装资源的分配与释放,在构造函数中完成资源的分配和初始化,在析构函数中完成资源的清理,它保证了资源正确的初始化和释放。
(2)智能指针不是指针,它实际上是一个模板,由智能指针实例化出来的对象具有和常规指针相似的行为,重点是智能指针负责自动的释放所指对象。
(3)由于智能指针的类都是栈上的对象,所以当函数(或程序)结束时会自动被释放。
三、智能指针的作用
- 以下三点学习理解智能指针:
(1)从较浅的层面看,智能指针是利用了一种叫做RAII(资源分配即初始化)的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。
(2)智能指针的作用是防止忘记调用delete释放内存和程序异常的进入catch块忘记释放内存。另外指针的释放时机也是非常有讲究的,多次释放同一个指针会造成程序崩溃,这些都可以通过智能指针来解决。
(3)智能指针还有一个作用是把值语义转换成引用语义。(这个是我在别的地方看到的,分享分享!)
C++和Java有一处最大的区别在于语义不同,在Java里面下列代码:
Animal a = new Animal();
Animal b = a
//这里只生成了一个对象
在C++中:
Animal a;
Animal b = a;
//这里生成了两个对象
四、常用的智能指针(C++11标准库)
1.auto_ptr
- 功能:指针管理权的转移, 是C++中最早的智能指针,存在很大问题。
- 不要使用!原因是:不支持复制(拷贝构造函数)和赋值(operator =),但复制或赋值的时候不会提示出错。因为不能被复制,所以不能被放入容器中。
- auto_ptr是通过权限转移的方式来防止由浅拷贝所带来的问题,所谓权限转移就是说开辟的内存在任何时刻都只能由一个指针指向。
还有个类似的智能指针:unique_ptr, 也不支持复制和赋值,但比auto_ptr好,直接赋值会编译出错。实在想赋值的话,需要使用:std::move。
2.scoped_ptr
- 概念:也称为防拷贝智能指针,它通过将赋值运算符的重载函数、拷贝构造函数声明设置为私有函数,只声明不定义,既防止类外的人为定义,同时阻止了拷贝。
- scoped_array是用来管理数组的。
3.shared_ptr
概念:通过引用计数实现的智能指针,功能强大,使用以及适用场景多。
通过引用计数来控制空间的释放,当一块空间创建时引用计数为1,当有新的指针指向这块空间时,引用计数加1,反之减1,直到引用计数减到0时才真的释放这块空间。
- shared_array用来管理数组的。
- shared_ptr的缺点:
引用计数存在线程安全问题和循环引用问题。
例如用智能指针实现一个双向链表,双向链表中的前驱和后继指针用的均是智能指针,则在销毁链表的时候,由于两个结点的销毁都依赖于对方先销毁,这时就会出现错误。
为了解决循环引用问题,我们可以使用weak_ptr,weak_ptr不增加引用计数。
4.weak_ptr
概念:为了解决shared_ptr指针的循环使用缺陷。
为什么不用原生指针替换weak_ptr?
弱引用并不修改该对象的引用计数,这意味着弱引用它并不对对象的内存进行管理。在功能上类似于普通指针,然而一个较大的区别是,弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存。
五、智能指针的实现
- 如下是基于引用计数的智能指针实现,需要实现构造、析构、拷贝构造、=操作符重载、重载*-和>操作符
template <typename T>
class SmartPointer {
public:
//构造函数
SmartPointer(T* p=0): _ptr(p), _reference_count(new size_t){
if(p)
*_reference_count = 1;
else
*_reference_count = 0;
}
//拷贝构造函数
SmartPointer(const SmartPointer& src) {
if(this!=&src) {
_ptr = src._ptr;
_reference_count = src._reference_count;
(*_reference_count)++;
}
}
//重载赋值操作符
SmartPointer& operator=(const SmartPointer& src) {
if(_ptr==src._ptr) {
return *this;
}
releaseCount();
_ptr = src._ptr;
_reference_count = src._reference_count;
(*_reference_count)++;
return *this;
}
//重载操作符
T& operator*() {
if(ptr) {
return *_ptr;
}
//throw exception
}
//重载操作符
T* operator->() {
if(ptr) {
return _ptr;
}
//throw exception
}
//析构函数
~SmartPointer() {
if (--(*_reference_count) == 0) {
delete _ptr;
delete _reference_count;
}
}
private:
T *_ptr;
size_t *_reference_count;
void releaseCount() {
if(_ptr) {
(*_reference_count)--;
if((*_reference_count)==0) {
delete _ptr;
delete _reference_count;
}
}
}
};
int main()
{
SmartPointer<char> cp1(new char('a'));
SmartPointer<char> cp2(cp1);
SmartPointer<char> cp3;
cp3 = cp2;
cp3 = cp1;
cp3 = cp3;
SmartPointer<char> cp4(new char('b'));
cp3 = cp4;
}