智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。C++ 11中最常用的智能指针类型为shared_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。该引用计数的内存在堆上分配。当新增一个时引用计数加1,当过期时引用计数减一。只有引用计数为0时,智能指针才会自动释放引用的内存资源。对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类。可以通过make_shared函数或者通过构造函数传入普通指针。并可以通过get函数获得普通指针。
C++四种智能指针auto_ptr、scope_ptr、shared_ptr和weak_ptr。其中auto_ptr是C++98标准化引入的(C++11中已经明确被废弃);scope_ptr、shared_ptr和weak_ptr是C++11标准化才引入的。
-
auto_ptr
auto_ptr的对象所有权是独占性的!
(这决定了不可能有两个auto_ptr对象同时拥有同一动态对象的所有权,仔细观察auto_ptr的源码就会发现拷贝构造和赋值操作符所接受的参数类型都是非const的引用类型(auto_ptr<_Ty>& ),而不是我们一般应该使用的const引用类型,也就是说被拷贝对象在拷贝过程中会被修改,拷贝物与被拷贝物之间是非等价的。)
缺点是:存在潜在的内存崩溃问题!
-
scope_ptr
auto_ptr可以几乎象scoped_ptr一样地工作,只要把auto_ptr声明为const。
区别在于:它不能转让所有权而auto_ptr可以。事实上,scoped_ptr永远不能被复制或被赋值!scoped_ptr 拥有它所指向的资源的所有权,并永远不会放弃这个所有权。
scoped_ptr的这种特性提升了我们的代码的表现,我们可以根据需要选择最合适的智能指针(scoped_ptr 或 auto_ptr)。
-
shared_ptr
提供一个标准的共享所有权的智能指针。(Boost库文档)
(shared_ptr就是为了解决auto_ptr在对象所有权上的局限性(auto_ptr是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针,当然这不会没有任何额外的代价……)
成员函数:
use_count 返回引用计数的个数
unique 返回是否是独占所有权( use_count 为 1)
swap 交换两个 shared_ptr 对象(即交换所拥有的对象)
reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少
get 返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.如 shared_ptr<int> sp(new int(1)); sp 与 sp.get()是等价的。
-
weak_ptr
weak_ptr是为配合shared_ptr而引入的一种智能指针来协助shared_ptr工作,它可以从一个shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会引起引用记数的增加或减少。没有重载*和->但可以使用lock获得一个可用的shared_ptr对象。
用途:
引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引用的对象。weak_ptr 的一个重要用途是“打破循环引用”,即解决 shared_ptr (引用计数)在循环引用中的“无能”。(通过boost::weak_ptr来打破循环引用:由于弱引用不更改引用计数,类似普通指针,只要把循环引用的一方使用弱引用,即可解除循环引用。)【强引用和弱引用】:
对于强引用来说,如果被引用的对象还活着,那么这个强引用也存在(就是说,当至少有一个强引用,那么这个对象就不能被释放)。boost::share_ptr就是强引用。
相对而言,对于弱引用来说,当引用的对象活着的时候弱引用不一定存在。仅仅是当它存在的时候的一个引用。弱引用并不修改该对象的引用计数,这意味这弱引用它并不对对象的内存进行管理,在功能上类似于普通指针,然而一个比较大的区别是,弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存。
-
内存泄露&解决方法
【内存泄露】当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。
【解决方法】为了解决循环引用导致的内存泄漏,引入了weak_ptr弱指针,weak_ptr的构造函数不会修改引用计数的值,从而不会对对象的内存进行管理,其类似一个普通指针,但不指向引用计数的共享内存,但是其可以检测到所管理的对象是否已经被释放,从而避免非法访问。
-
shared_ptr的实现
核心要理解引用计数,什么时候销毁底层指针,还有赋值,拷贝构造时候的引用计数的变化,析构的时候要判断底层指针的引用计数为0了才能真正释放底层指针的内存
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; }