智能指针及其作用
一、引入背景
使用 C++ 的指针可以动态开辟存储空间,但若在使用完毕后忘记释放(或在释放之前,程序 throw 出错误,导致没有释放),导致该内存单元一直被占据直到程序结束,即发生了所谓的内存泄漏。
【注】内存泄漏是指堆内存的泄漏。堆,就是那些由 new 分配的内存块。
因此智能指针的作用就是为了保证使用堆上对象的时候,对象一定会被释放,但只能释放一次,并且释放后指向该对象的指针应该马上归 0。
二、C++11 以前的智能指针
auto_ptr,指向一个动态分配的对象指针,它的析构函数用于删除所指对象的空间,以此达到对对象生存期的控制。
已被弃用,原因是:
- 避免潜在的内存崩溃:auto_ptr 进行赋值操作时候,被赋值的取得其所有权,去赋值的丢失其所有权(变成空指针,无法再使用);
- 不够方便:没有移动语义(后面讲)。
三、C++11 标准的智能指针
加入了 unique_ptr、shared_ptr 和 weak_ptr。
-
unique_ptr,等于 boost 库中的 scoped_ptr,正如其名字所述,scoped_ptr 所指向的对象在作用域之外会自动得到析构。
【注】boost 库是 Boost 社区在 C++11 之前嫌弃标准更新太慢,而自发组织开发、维护的一个扩展库。通过包含头文件”boost/scoped_ptr.hpp”
来引入。
简单定义:
namespace boost
{
// scoped_ptr 是 non-copyable 的
// 也就是说你不能去尝试复制一个 scoped_ptr 的内容到另外一个 scoped_ptr 中
// 这也是为了防止错误地多次析构同一个指针所指向的对象
template<typename T> class scoped_ptr : noncopyable
{
private:
T *px;
// scoped_ptr 不能通过其他 scoped_ptr 共享控制权
// 因此在 scoped_ptr 类的内部将拷贝构造函数和 = 运算符重载定义为 private
scoped_ptr(scoped_ptr const &);
scoped_ptr &operator=(scoped_ptr const &);
typedef scoped_ptr<T> this_type;
void operator==( scoped_ptr const & ) const;
void operator!=( scoped_ptr const & ) const;
public:
// 构造函数显示定义,不能通过隐式转换(赋值操作符 =)构造
explicit scoped_ptr(T *p = 0);
~scoped_ptr();
explicit scoped_ptr( std::auto_ptr<T> p ): px( p.release() );
// 在 scoped_ptr 离开作用域之前也可以显式销毁它们所管理的对象,调用它的 reset 方法即可
void reset(T *p = 0);
T &operator*() const;
T *operator->() const;
T *get() const;
// 虽然 scoped_ptr 不能转移控制权,但是它们可以交换控制权
void swap(scoped_ptr &b);
};
template<typename T>
void swap(scoped_ptr<T> &a, scoped_ptr<T> &b);
}
-
shared_ptr,可共享指针对象,可以赋值给 shared_ptr 或 weak_ptr。shared_ptr 中所实现的本质是引用计数,拷贝一个 shared_ptr 将对这个智能指针的引用次数加 1,而当这个智能指针的引用次数降低到 0 的时候,该对象自动被析构。
代码部分与 scoped_ptr 类似,只是支持拷贝、赋值和==、!=。 -
weak_ptr
weak_ptr 和 shared_ptr 的最大区别在于 weak_ptr 在指向一个对象的时候不会增加其引用计数,因此你可以用 weak_ptr 去指向一个对象并且在 weak_ptr 仍然指向这个对象的时候析构它,此时你再访问 weak_ptr 的时候,weak_ptr 返回的会是一个空的 shared_ptr。
常被用来解决环状引用问题,告诉 C++ 环上哪一个引用是最弱的,因此在一个环上把原来的某一个 shared_ptr 改成 weak_ptr 就可以了。
【注】以上 std 标准库的智能指针都可以通过包含头文件 <memory>
来引入。