简介C++中的智能指针
本文主要介绍C++标准中的3种智能指针: unique_ptr, shared_ptr, weak_ptr
至于早期的 auto_ptr (在C++11标准中被列为 deprecated) 和 boost::scoped_ptr (不属于C++标准),不在本文范围内。
智能指针的原理及RAII,属于比较基础的内容,不再多说。
要使用这3种智能指针,必须包含头文件
顾名思义,
- unique_ptr 的拷贝构造函数 和 参数为左值引用的 operator = 都是被禁止的。
- 而参数为右值引用的 operator = 是可以的。 如下:
unique_ptr (const unique_ptr&) = delete; unique_ptr& operator= (const unique_ptr&) = delete; // 参数为右值引用的赋值操作符 unique_ptr& operator= (unique_ptr&& x) noexcept; templateunique_ptr& operator= (unique_ptr&& x) noexcept;
由上可见,
一个左值的 unique_ptr 不能被赋值给另外一个 unique_ptr, 否则编译报错;
但右值的 unique_ptr 可以被赋值给另外的 unique_ptr, 因为作为右值,它马上就会消亡了;
所以使用 std::move 将左值转为右值再进行赋值也是可以的,但要注意,std::move 后的 unique_ptr 不能再被使用。
unique_ptrp3 (new string("AAA")); unique_ptr p4; p4 = p3; // 编译报错!以左值引用为参数的赋值已被禁止。 p4 = unique_ptr (new string("BBB")); // OK p4 = std::move(p3); // OK,但 p3 相当于悬挂指针,不能再被使用。
对于 unique_ptr 的初始化,也可以使用 make_unique ,会减少一次对指针的内存拷贝。
unique_ptrp = make_unique (10);
make_unique 是到C++14才支持的。
如果要自己手写一个 make_unique, 可以如下:
templatestd::unique_ptr make_unique( Args&& ...args) { return std::unique_ptr ( new T( std::forward (args)... ) ); }
另外,unique_ptr可以通过构造函数指定对资源的deleter;
这一般并不常见,故本文不做展开,但也有一些示例如下。
以下是关于 unique_ptr 构造的示例程序:
#include1.2 重要成员函数:#include int main () { std::default_delete d; std::unique_ptr u1; std::unique_ptr u2 (nullptr); std::unique_ptr u3 (new int); std::unique_ptr u4 (new int, d); // 带 deleter 的构造 std::unique_ptr u5 (new int, std::default_delete ()); // 带 deleter 的构造 std::unique_ptr u6 (std::move(u5)); std::unique_ptr u7 (std::move(u6)); std::cout << "u1: " << (u1?"not null":"null") << 'n'; std::cout << "u2: " << (u2?"not null":"null") << 'n'; std::cout << "u3: " << (u3?"not null":"null") << 'n'; std::cout << "u4: " << (u4?"not null":"null") << 'n'; std::cout << "u5: " << (u5?"not null":"null") << 'n'; std::cout << "u6: " << (u6?"not null":"null") << 'n'; std::cout << "u7: " << (u7?"not null":"null") << 'n'; return 0; }
-
operator * : 给指针解引用
-
operator -> : 通过指针调用成员函数/成员
-
operator bool : 查看所管理指针是否为 nullptr, 相当于 get() != nullptr
-
get(): 返回所管理指针
-
release(): 不再管理指针,注意,但也不会去释放所管理指针指向的空间;将所管理指针替换为 nullptr
-
reset(p=nullptr): 将原来管理的指针(如果有的话)的空间释放掉,然后管理新传入的指针p
-
swap(unique_ptr& x): 交换所管理的指针
pointer get() const noexcept; pointer release() noexcept; void reset (pointer p = pointer()) noexcept; void swap (unique_ptr& x) noexcept; explicit operator bool() const noexcept;2. shared_ptr 2.1 常用的构造函数
常用的构造函数大致包括: 传入的参数为空、传入参数为new的指针、shared_ptr(左值或右值), weak_ptr(左值), unique_ptr(右值).
另有带 deleter 的构造函数、带 allocator 的构造函数、以及通过aliasing进行构造的构造函数,这些不在本文展开。
关于 aliasing 构造函数 和 owner_before 成员函数,可以参考笔者此前的一篇博文。
// 默认构造函数
constexpr shared_ptr() noexcept;
// 从 nullptr 进行构造
constexpr shared_ptr(nullptr_t) : shared_ptr() {}
// 从传入的指针进行构造
template
explicit shared_ptr (U* p);
// 拷贝构造函数 (通过常量左值引用进行构造)
shared_ptr (const shared_ptr& x) noexcept;
template
shared_ptr (const shared_ptr& x) noexcept;
// 从 weak_ptr 进行构造
template
explicit shared_ptr (const weak_ptr& x);
// 移动构造
shared_ptr (shared_ptr&& x) noexcept;
template
shared_ptr (shared_ptr&& x) noexcept;
// 从 unique_ptr 构造,也是移动构造
template
shared_ptr (unique_ptr&& x);
另外,可以使用 std::make_shared 进行构造,它用来消除显式的使用 new,所以 make_shared 会根据传入的参数来创建对象,然后返回指向该对象的 shared_ptr.
之所以推荐使用 make_shared, 是可以减少一次对指针的内存拷贝。
std::shared_ptrp = std::make_shared (20);
以下是示例程序:
#include2.2 重要的成员函数#include struct C {int* data;}; int main () { std::shared_ptr p1; std::shared_ptr p2 (nullptr); std::shared_ptr p3 (new int); std::shared_ptr p4 (new int, std::default_delete ()); // 带 deleter的构造函数 std::shared_ptr p5 (new int, [](int* p){delete p;}, std::allocator ()); // 带 deleter的构造函数 std::shared_ptr p6 (p5); std::shared_ptr p7 (std::move(p6)); std::shared_ptr p8 (std::unique_ptr (new int)); std::shared_ptr obj (new C); std::shared_ptr p9 (obj, obj->data); // aliasing 构造函数 std::cout << "use_count:n"; std::cout << "p1: " << p1.use_count() << 'n'; // 0 std::cout << "p2: " << p2.use_count() << 'n'; // 0 std::cout << "p3: " << p3.use_count() << 'n'; // 1 std::cout << "p4: " << p4.use_count() << 'n'; // 1 std::cout << "p5: " << p5.use_count() << 'n'; // 2 std::cout << "p6: " << p6.use_count() << 'n'; // 0 std::cout << "p7: " << p7.use_count() << 'n'; // 2 std::cout << "p8: " << p8.use_count() << 'n'; // 1 std::cout << "obj: " << obj.use_count() << 'n'; // 2 std::cout << "p9: " << p9.use_count() << 'n'; // 2 return 0; }
-
operator = : 其参数主要是常量左值引用、右值引用、unique_ptr的右值引用;
-
operator * : 指针解引用,等于调用 *get()
-
operator ->: 调用指针的成员函数/成员
-
operator bool: 检查是否为nullptr
-
get(): 返回所管理的指针。使用 sp 和 sp.get() 是一样的。
-
reset(): 不再管理原来的指针,计数减一
-
reset( p ): 不再管理原来的指针,而开始管理新传入的指针p,即原来的指针计数减一,新的指针的计数加一
-
swap(x): 交换所管理的指针
-
use_count(): 查看资源所有者个数。
-
unique(): 返回是否是独占所有权,是为1,否为0
element_type* get() const noexcept; void reset() noexcept; templatevoid reset (U* p); void swap (shared_ptr& x) noexcept; long int use_count() const noexcept; bool unique() const noexcept;
总结一下, shared_ptr 与 unique_ptr 相同或相近的成员函数如下:
- get()
- reset() / reset( p )
- swap§
- operator *
- operator ->
- operator bool
unique_ptr 有而 shared_ptr 没有的成员函数:
- release()
shared_ptr 有而 unique_ptr 没有的成员函数:
- use_count()
- unique()
shared_ptr的实现: 关键是它包含了一个计数器类的指针,所指的空间在堆上;多个shared_ptr都是共享这个指针所指向的counter
shared_ptr 的实现图如下:
该图中也给出了计数器类的实现,即一个计数器既包括各个shared_ptr共享的use_count,也包括weak_ptr共享的weak_count.
2.4 shared_ptr 是不是线程安全的陈硕的这篇文章有详细论述。
这里只给出结论:
shared_ptr 的引用计数本身是安全且无锁的,但对象的读写则不是;
因为 shared_ptr 有2个数据成员(即计数器指针和所管理的指针),所以读写操作不能原子化;
如果多个线程读写同一个 shared_ptr 对象,那么需要加锁。
当2个对象各使用一个shared_ptr成员变量指向对方,就会造成循环引用,使得这2个shared_ptr的引用计数永远不可能降为0,从而导致内存泄漏。
weak_ptr就是用来解决shared_ptr相互引用时的死锁问题,它是一种弱引用,不会增加对象的引用计数。
以上问题的解决方案就是,将其中一个 shared_ptr 改为 weak_ptr.
- weak_ptr的构造、析构、拷贝、赋值,都不会引起引用记数的增加或减少;
- weak_ptr 只可以从一个 shared_ptr 或另一个 weak_ptr 对象进行构造或赋值;
- weak_ptr 只有拷贝构造,而没有也不需要有移动构造
// 构造 constexpr weak_ptr() noexcept; // 拷贝构造,没有移动构造 weak_ptr (const weak_ptr& x) noexcept; template3.2 常用的成员函数weak_ptr (const weak_ptr& x) noexcept; template weak_ptr (const shared_ptr& x) noexcept; // 赋值操作符 weak_ptr& operator= (const weak_ptr& x) noexcept; template weak_ptr& operator= (const weak_ptr& x) noexcept; template weak_ptr& operator= (const shared_ptr& x) noexcept;
-
weak_ptr和shared_ptr之间可以相互转化:
shared_ptr 可以直接赋值给 weak_ptr;
weak_ptr 通过调用lock()函数来获得 shared_ptr -
不能通过weak_ptr直接访问对象的成员函数/成员,而必须通过lock()函数先转成shared_ptr,然后再访问对象的成员函数/成员。
-
weak_ptr 没有重载 operator * 和 operator ->
-
weak_ptr 在使用前需要检查合法性,即调用 expired(),它用于检测所管理的对象是否已经释放
-
use_count(): 返回对象的引用计数.
-
reset(): 将 weak_ptr 置空.
-
swap(): 交换 shared_ptr
shared_ptrlock() const noexcept; bool expired() const noexcept; long int use_count() const noexcept; void reset() noexcept; void swap (weak_ptr& x) noexcept;
(完)



