简介C++中的智能指针
本文主要介绍C++标准中的3种智能指针: unique_ptr, shared_ptr, weak_ptr 至于早期的 auto_ptr (在C++11标准中被列为 deprecated) 和 boost::scoped_ptr (不属于C++标准),不在本文范围内。 智能指针的原理及RAII,属于比较基础的内容,不再多说。
要使用这3种智能指针,必须包含头文件 <memory>.
1. unique_ptr
1.1 常用的构造方法与赋值操作符
顾名思义,
- unique_ptr 的拷贝构造函数 和 参数为左值引用的 operator = 都是被禁止的。
- 而参数为右值引用的 operator = 是可以的。 如下:
unique_ptr (const unique_ptr&) = delete;
unique_ptr& operator= (const unique_ptr&) = delete;
unique_ptr& operator= (unique_ptr&& x) noexcept;
template <class U, class E>
unique_ptr& operator= (unique_ptr<U,E>&& x) noexcept;
由上可见, 一个左值的 unique_ptr 不能被赋值给另外一个 unique_ptr, 否则编译报错; 但右值的 unique_ptr 可以被赋值给另外的 unique_ptr, 因为作为右值,它马上就会消亡了; 所以使用 std::move 将左值转为右值再进行赋值也是可以的,但要注意,std::move 后的 unique_ptr 不能再被使用。
unique_ptr<string> p3 (new string("AAA"));
unique_ptr<string> p4;
p4 = p3;
p4 = unique_ptr<string>(new string("BBB"));
p4 = std::move(p3);
对于 unique_ptr 的初始化,也可以使用 make_unique ,会减少一次对指针的内存拷贝。
unique_ptr<int> p = make_unique<int>(10);
make_unique 是到C++14才支持的。 如果要自己手写一个 make_unique, 可以如下:
template <typename T, typename ...Args>
std::unique_ptr<T> make_unique( Args&& ...args) {
return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
}
另外,unique_ptr可以通过构造函数指定对资源的deleter; 这一般并不常见,故本文不做展开,但也有一些示例如下。
以下是关于 unique_ptr 构造的示例程序:
#include <iostream>
#include <memory>
int main () {
std::default_delete<int> d;
std::unique_ptr<int> u1;
std::unique_ptr<int> u2 (nullptr);
std::unique_ptr<int> u3 (new int);
std::unique_ptr<int> u4 (new int, d);
std::unique_ptr<int> u5 (new int, std::default_delete<int>());
std::unique_ptr<int> u6 (std::move(u5));
std::unique_ptr<int> 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;
}
1.2 重要成员函数:
-
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;
constexpr shared_ptr(nullptr_t) : shared_ptr() {}
template <class U>
explicit shared_ptr (U* p);
shared_ptr (const shared_ptr& x) noexcept;
template <class U>
shared_ptr (const shared_ptr<U>& x) noexcept;
template <class U>
explicit shared_ptr (const weak_ptr<U>& x);
shared_ptr (shared_ptr&& x) noexcept;
template <class U>
shared_ptr (shared_ptr<U>&& x) noexcept;
template <class U, class D>
shared_ptr (unique_ptr<U,D>&& x);
另外,可以使用 std::make_shared 进行构造,它用来消除显式的使用 new,所以 make_shared 会根据传入的参数来创建对象,然后返回指向该对象的 shared_ptr. 之所以推荐使用 make_shared, 是可以减少一次对指针的内存拷贝。
std::shared_ptr<int> p = std::make_shared<int> (20);
以下是示例程序:
#include <iostream>
#include <memory>
struct C {int* data;};
int main () {
std::shared_ptr<int> p1;
std::shared_ptr<int> p2 (nullptr);
std::shared_ptr<int> p3 (new int);
std::shared_ptr<int> p4 (new int, std::default_delete<int>());
std::shared_ptr<int> p5 (new int, [](int* p){delete p;}, std::allocator<int>());
std::shared_ptr<int> p6 (p5);
std::shared_ptr<int> p7 (std::move(p6));
std::shared_ptr<int> p8 (std::unique_ptr<int>(new int));
std::shared_ptr<C> obj (new C);
std::shared_ptr<int> p9 (obj, obj->data);
std::cout << "use_count:\n";
std::cout << "p1: " << p1.use_count() << '\n';
std::cout << "p2: " << p2.use_count() << '\n';
std::cout << "p3: " << p3.use_count() << '\n';
std::cout << "p4: " << p4.use_count() << '\n';
std::cout << "p5: " << p5.use_count() << '\n';
std::cout << "p6: " << p6.use_count() << '\n';
std::cout << "p7: " << p7.use_count() << '\n';
std::cout << "p8: " << p8.use_count() << '\n';
std::cout << "obj: " << obj.use_count() << '\n';
std::cout << "p9: " << p9.use_count() << '\n';
return 0;
}
2.2 重要的成员函数
-
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;
template <class U> void 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 没有的成员函数:
shared_ptr 有而 unique_ptr 没有的成员函数:
2.3 shared_ptr 的实现
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 对象,那么需要加锁。
3. weak_ptr
当2个对象各使用一个shared_ptr成员变量指向对方,就会造成循环引用,使得这2个shared_ptr的引用计数永远不可能降为0,从而导致内存泄漏。 weak_ptr就是用来解决shared_ptr相互引用时的死锁问题,它是一种弱引用,不会增加对象的引用计数。 以上问题的解决方案就是,将其中一个 shared_ptr 改为 weak_ptr.
3.1 关于构造与赋值
- weak_ptr的构造、析构、拷贝、赋值,都不会引起引用记数的增加或减少;
- weak_ptr 只可以从一个 shared_ptr 或另一个 weak_ptr 对象进行构造或赋值;
- weak_ptr 只有拷贝构造,而没有也不需要有移动构造
constexpr weak_ptr() noexcept;
weak_ptr (const weak_ptr& x) noexcept;
template <class U> weak_ptr (const weak_ptr<U>& x) noexcept;
template <class U> weak_ptr (const shared_ptr<U>& x) noexcept;
weak_ptr& operator= (const weak_ptr& x) noexcept;
template <class U> weak_ptr& operator= (const weak_ptr<U>& x) noexcept;
template <class U> weak_ptr& operator= (const shared_ptr<U>& x) noexcept;
3.2 常用的成员函数
-
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_ptr<element_type> lock() const noexcept;
bool expired() const noexcept;
long int use_count() const noexcept;
void reset() noexcept;
void swap (weak_ptr& x) noexcept;
(完)
|