一、为什么使用智能指针
在C++中,堆上申请内存之后,需要手动对内存进行释放,随着代码逐渐复杂,很难保证所有的内存都被释放。
智能指针设计的初衷就是可以帮助我们管理堆上申请的内存,可以理解为开发者只需要申请,而释放交给智能指针。
二、unique_ptr,shared_ptr,weak_ptr
unique_ptr
unique_ptr 拥有对 持有对象的唯一所有权,即两个 unique_ptr 不能同时指向同一个对象
1、unique_ptr不能被复制到另外一个unique_ptr
2、unique_ptr所持有的对象只能通过转移语义将所有权转移到另外一个unique_ptr
智能指针有一个通用的规则,就是->表示用于调用指针原有的方法,而 . 则表示调用智能指针本身的方法。
unique_ptr本身拥有的方法主要包括:
1、get() 获取其保存的原生指针,尽量不要使用
2、bool() 判断是否拥有指针
3、release() 释放所管理指针的所有权,返回原生指针。但并不销毁原生指针。
4、reset() 释放并销毁原生指针。如果参数为一个新指针,将管理这个新指针
unique_ptr 一般在不需要多个指向同一个对象的指针时使用,但这个条件本身就很难判断,在我看来可以简单的理解:这个对象 / 在 /对象或方法内部使用时优先使用 unique_ptr
1、对象内部使用
class TestUnique
{
private:
std::unique_ptr<A> a_ = std::unique_ptr<A>(new A());
public:
void process1()
{
a_->do_something();
}
void process2()
{
a_->do_something();
}
~TestUnique()
{
//此处不再需要手动删除a_
}
};
2、方法内部使用
void test_unique_ptr()
{
std::unique_ptr<A> a(new A());
a->do_something();
}
shared_ptr
与unique_ptr的唯一所有权所不同的是,shared_ptr强调的是共享所有权。也就是说多个shared_ptr可以拥有同一个原生指针的所有权。
std::shared_ptr<A> a1(new A());
std::shared_ptr<A> a2 = a1;//编译正常,允许所有权的共享
shared_ptr 是通过引用计数的方式管理指针,当引用计数为 0 时会销毁拥有的原生对象。
shared_ptr本身拥有的方法主要包括:
1、get() 获取其保存的原生指针,尽量不要使用
2、bool() 判断是否拥有指针
3、reset() 释放并销毁原生指针。如果参数为一个新指针,将管理这个新指针
4、unique() 如果引用计数为 1,则返回 true,否则返回 false
5、use_count() 返回引用计数的大小
std::shared_ptr<A> a1(new A());
std::shared_ptr<A> a2 = a1;//编译正常,允许所有权的共享
A *origin_a = a1.get();//尽量不要暴露原生指针
if(a1)
{
// a1 拥有指针
}
if(a1.unique())
{
// 如果返回true,引用计数为1
}
long a1_use_count = a1.use_count();//引用计数数量
shared_ptr 的内存占用是裸指针的两倍,因为除了要管理一个裸指针外,还需要维护一个 引用计数 ,因此相比于unique_ptr,shared_ptr的内存占用更高。原子操作性能低。考虑到线程安全问题,引用计数的增减必须是原子操作。而原子操作一般情况下都比非原子操作慢。
使用场景:1、shared_ptr 通常使用在共享权不明的场景。有可能多个对象同时管理同一个内存时。
2、对象的延迟销毁。陈硕在《Linux 多线程服务器端编程》中提到,当一个对象的析构非常耗时,甚至影响到了关键线程的速度。可以使用 BlockingQueue<std::shared_ptr> 将对象转移到另外一个线程中释放,从而解放关键线程。
shared_ptr的使用场景
shared_ptr一般在需要多个执行同一个对象的指针使用,在我看来可以简单的理解:这个对象需要被多个class 同时使用的时候
class B
{
private:
std::shared_ptr<A> a_;
public:
B(std::shared_ptr<A>& a): a_(a) {}
};
class C
{
private:
std::shared_ptr<A> a_;
public:
C(std::shared_ptr<A>& a): a_(a) {}
};
std::shared_ptr<B> b_;
std::shared_ptr<C> c_;
void test_A_B_C()
{
std::shared_ptr<A> a = std::make_shared<A>();
b_ = std::make_shared<B>(a);
c_ = std::make_shared<C>(a);
}
weak_ptr
weak_ptr 比较特殊,它主要是为了配合shared_ptr而存在的。就像它的名字一样,它本身是一个弱指针,因为它本身是不能直接调用原生指针的方法的。如果想要使用原生指针的方法,需要将其先转换为一个shared_ptr。那weak_ptr存在的意义到底是什么呢?
由于shared_ptr是通过引用计数来管理原生指针的,那么最大的问题就是循环引用(比如 a 对象持有 b 对象,b 对象持有 a 对象),这样必然会导致内存泄露。而weak_ptr不会增加引用计数,因此将循环引用的一方修改为弱引用,可以避免内存泄露。
weak_ptr可以通过一个shared_ptr创建。
std::shared_ptr<A> a1(new A());
std::weak_ptr<A> weak_a1 = a1;//不增加引用计数
weak_ptr本身拥有的方法主要包括:
1、expired() 判断所指向的原生指针是否被释放,如果被释放了返回 true,否则返回 false
2、use_count() 返回原生指针的引用计数
3、lock() 返回 shared_ptr,如果原生指针没有被释放,则返回一个非空的 shared_ptr,否则返回一个空的 shared_ptr
std::shared_ptr<A> a1(new A());
std::wead_ptr<A> weak_a1 = a1; //并不增加引用计数
if(weak_a1.expired()){
//如果为true,weak_a1对应的原生指针已经被释放了
}
long a1_use_count = weak_a1.use_count(); // 引用计数量
if(std::shared_ptr<A> shared_a = weak_a1.lock()){
//此时可以通过 shared_a 进行原生指针的方法调用
}
weak_a1.reset(); // 将 weak_a1 置空
三、一些关于智能指针的点
shared_ptr对象在内部指向两个内存位置
1、指向对象的指针
2、用于控制引用计数数据的指针
创建 shared_ptr 对象
- 使用原始指针创建shared_ptr 对象
- 带有参数的shared_ptr 构造函数是 explicit 类型的,所以不能像 std::shared_ptr p1 = new int() 创建,应该
std::shared_ptr<int> ptr2(new int());
此方法在堆上创建了两块内存:1:存储int。2:控制块上用于引用计数的内存,管理附加此内存的 shared_ptr 对象的计数,最初计数将为1。
使用make_shared
std::shared_ptr<int> ptr = std::make_shared<int>();
std::make_shared 一次性为 int对象 和 用于引用计数 的数据都分配了内存,而 new 操作符只是为int分配了内存。
因为使用new的方式创建shared_ptr会导致出现两次内存申请,而std::make_shared在内部实现时只会申请一个内存。因此建议后续均使用std::make_shared。
可以给 shared_ptr 添加自定义删除器
当 shared_ptr 对象调用其析构函数,它将引用计数减1,如果引用计数的新值为0,则删除关联的原始指针。析构函数中删除内部原始指针,默认调用的是delete()函数。 当shared_ptr指向数组时,该函数针对数组类型创建的指针是不起作用的,需要自己来自定义一个删除器来delete[].
//删除器
void deleter(Sample * x)
{
std::cout << "delete\n";
delete[] x;
}
//第二个参数传递自定义删除器指针
std::shared_ptr<Sample> p(new Sample[2], deleter);
四、源码解析
uniptr_ptr
有两个模板参数,分别为 _Tp 和 _Dp
- _Tp表示原生指针的类型
- _Dp则表示析构器,开发者则可以自定义指针 销毁的代码,其拥有一个默认值 default_delete<_Tp> ,其实就是标准的 delete 函数
函数声明中 typename __pointer_type<_Tp, deleter_type>::type 可以简单理解为 _Tp*,即原生指针类型
template <class _Tp, class _Dp = default_delete<_Tp> >
class _LIBCPP_TEMPLATE_VIS unique_ptr {
public:
typedef _Tp element_type;
typedef _Dp deleter_type;
typedef typename __pointer_type<_Tp, deleter_type>::type pointer;
//...
}
unique_ptr中唯一的数据成员就是原生指针和析构器的 pair。
private:
__compressed_pair<pointer, deleter_type> __ptr_;
下面看下unique_ptr的构造函数。
template <class _Tp, class _Dp = default_delete<_Tp> >
class _LIBCPP_TEMPLATE_VIS unique_ptr {
public:
// 默认构造函数,用pointer的默认构造函数初始化__ptr_
constexpr unique_ptr() noexcept : __ptr_(pointer()) {}
// 空指针的构造函数,同上
constexpr unique_ptr(nullptr_t) noexcept : __ptr_(pointer()) {}
// 原生指针的构造函数,用原生指针初始化__ptr_
explicit unique_ptr(pointer __p) noexcept : __ptr_(__p) {}
// 原生指针和析构器的构造函数,用这两个参数初始化__ptr_,当前析构器为左值引用
unique_ptr(pointer __p, _LValRefType<_Dummy> __d) noexcept
: __ptr_(__p, __d) {}
// 原生指针和析构器的构造函数,析构器使用转移语义进行转移
unique_ptr(pointer __p, _GoodRValRefType<_Dummy> __d) noexcept
: __ptr_(__p, _VSTD::move(__d)) {
static_assert(!is_reference<deleter_type>::value,
"rvalue deleter bound to reference");
}
// 移动构造函数,取出原有unique_ptr的指针和析构器进行构造
unique_ptr(unique_ptr&& __u) noexcept
: __ptr_(__u.release(), _VSTD::forward<deleter_type>(__u.get_deleter())) {
}
// 移动赋值函数,取出原有unique_ptr的指针和析构器进行构造
unique_ptr& operator=(unique_ptr&& __u) _NOEXCEPT {
reset(__u.release());
__ptr_.second() = _VSTD::forward<deleter_type>(__u.get_deleter());
return *this;
}
}
再看下unique_ptr几个常用函数的实现。
template <class _Tp, class _Dp = default_delete<_Tp> >
class _LIBCPP_TEMPLATE_VIS unique_ptr {
// 返回原生指针
pointer get() const _NOEXCEPT {
return __ptr_.first();
}
// 判断原生指针是否为空
_LIBCPP_EXPLICIT operator bool() const _NOEXCEPT {
return __ptr_.first() != nullptr;
}
// 将__ptr置空,并返回原有的指针
pointer release() _NOEXCEPT {
pointer __t = __ptr_.first();
__ptr_.first() = pointer();
return __t;
}
// 重置原有的指针为新的指针,如果原有指针不为空,对原有指针所指对象进行销毁
void reset(pointer __p = pointer()) _NOEXCEPT {
pointer __tmp = __ptr_.first();
__ptr_.first() = __p;
if (__tmp)
__ptr_.second()(__tmp);
}
}
再看下unique_ptr指针特性的两个方法。
// 返回原生指针的引用
typename add_lvalue_reference<_Tp>::type
operator*() const {
return *__ptr_.first();
}
// 返回原生指针
pointer operator->() const _NOEXCEPT {
return __ptr_.first();
}
最后再看下unique_ptr的析构函数。
// 通过reset()方法进行对象的销毁
~unique_ptr() { reset(); }
shared_ptr
shared_ptr 与unique_ptr最核心的区别就是比unique_ptr多了一个引用计数,并由于引用计数的加入,可以支持拷贝。
先看下shared_ptr的声明。shared_ptr主要有两个成员变量,一个是原生指针,一个是控制块的指针,用来存储这个原生指针的shared_ptr和weak_ptr的数量。
template<class _Tp>
class shared_ptr
{
public:
typedef _Tp element_type;
private:
element_type* __ptr_;
__shared_weak_count* __cntrl_;
//...
}
我们重点看下__shared_weak_count的定义。
// 共享计数类
class __shared_count
{
__shared_count(const __shared_count&);
__shared_count& operator=(const __shared_count&);
protected:
// 共享计数
long __shared_owners_;
virtual ~__shared_count();
private:
// 引用计数变为0的回调,一般是进行内存释放
virtual void __on_zero_shared() _NOEXCEPT = 0;
public:
// 构造函数,需要注意内部存储的引用计数是从0开始,外部看到的引用计数其实为1
explicit __shared_count(long __refs = 0) _NOEXCEPT
: __shared_owners_(__refs) {}
// 增加共享计数
void __add_shared() _NOEXCEPT {
__libcpp_atomic_refcount_increment(__shared_owners_);
}
// 释放共享计数,如果共享计数为0(内部为-1),则调用__on_zero_shared进行内存释放
bool __release_shared() _NOEXCEPT {
if (__libcpp_atomic_refcount_decrement(__shared_owners_) == -1) {
__on_zero_shared();
return true;
}
return false;
}
// 返回引用计数,需要对内部存储的引用计数+1处理
long use_count() const _NOEXCEPT {
return __libcpp_relaxed_load(&__shared_owners_) + 1;
}
};
class __shared_weak_count
: private __shared_count
{
// weak ptr计数
long __shared_weak_owners_;
public:
// 内部共享计数和weak计数都为0
explicit __shared_weak_count(long __refs = 0) _NOEXCEPT
: __shared_count(__refs),
__shared_weak_owners_(__refs) {}
protected:
virtual ~__shared_weak_count();
public:
// 调用通过父类的__add_shared,增加共享引用计数
void __add_shared() _NOEXCEPT {
__shared_count::__add_shared();
}
// 增加weak引用计数
void __add_weak() _NOEXCEPT {
__libcpp_atomic_refcount_increment(__shared_weak_owners_);
}
// 调用父类的__release_shared,如果释放了原生指针的内存,还需要调用__release_weak,因为内部weak计数默认为0
void __release_shared() _NOEXCEPT {
if (__shared_count::__release_shared())
__release_weak();
}
// weak引用计数减1
void __release_weak() _NOEXCEPT;
// 获取共享计数
long use_count() const _NOEXCEPT {return __shared_count::use_count();}
__shared_weak_count* lock() _NOEXCEPT;
private:
// weak计数为0的处理
virtual void __on_zero_shared_weak() _NOEXCEPT = 0;
};
其实__shared_weak_count也是虚类,具体使用的是__shared_ptr_pointer。_shared_ptr_pointer 中有一个成员变量__data,用于存储原生指针、析构器、分配器。__shared_ptr_pointer继承了__shared_weak_count,因此它就主要负责内存的分配、销毁,引用计数。
class __shared_ptr_pointer
: public __shared_weak_count
{
__compressed_pair<__compressed_pair<_Tp, _Dp>, _Alloc> __data_;
public:
_LIBCPP_INLINE_VISIBILITY
__shared_ptr_pointer(_Tp __p, _Dp __d, _Alloc __a)
: __data_(__compressed_pair<_Tp, _Dp>(__p, _VSTD::move(__d)), _VSTD::move(__a)) {}
#ifndef _LIBCPP_NO_RTTI
virtual const void* __get_deleter(const type_info&) const _NOEXCEPT;
#endif
private:
virtual void __on_zero_shared() _NOEXCEPT;
virtual void __on_zero_shared_weak() _NOEXCEPT;
};
了解了引用计数的基本原理后,再看下shared_ptr的实现。
// 使用原生指针构造shared_ptr时,会构建__shared_ptr_pointer的控制块
shared_ptr<_Tp>::shared_ptr(_Yp* __p,
typename enable_if<is_convertible<_Yp*, element_type*>::value, __nat>::type)
: __ptr_(__p)
{
unique_ptr<_Yp> __hold(__p);
typedef typename __shared_ptr_default_allocator<_Yp>::type _AllocT;
typedef __shared_ptr_pointer<_Yp*, default_delete<_Yp>, _AllocT > _CntrlBlk;
__cntrl_ = new _CntrlBlk(__p, default_delete<_Yp>(), _AllocT());
__hold.release();
__enable_weak_this(__p, __p);
}
// 如果进行shared_ptr的拷贝,会增加引用计数
template<class _Tp>
inline
shared_ptr<_Tp>::shared_ptr(const shared_ptr& __r) _NOEXCEPT
: __ptr_(__r.__ptr_),
__cntrl_(__r.__cntrl_)
{
if (__cntrl_)
__cntrl_->__add_shared();
}
// 销毁shared_ptr时,会使共享引用计数减1,如果减到0会销毁内存
template<class _Tp>
shared_ptr<_Tp>::~shared_ptr()
{
if (__cntrl_)
__cntrl_->__release_shared();
}
weak_ptr
了解完shared_ptr,weak_ptr也就比较简单了。weak_ptr也包括两个对象,一个是原生指针,一个是控制块。虽然weak_ptr内存储了原生指针,不过由于未实现operator->因此不能直接使用。
class _LIBCPP_TEMPLATE_VIS weak_ptr
{
public:
typedef _Tp element_type;
private:
element_type* __ptr_;
__shared_weak_count* __cntrl_;
}
// 通过shared_ptr构造weak_ptr。会将shared_ptr的成员变量地址进行复制。增加weak引用计数
weak_ptr<_Tp>::weak_ptr(shared_ptr<_Yp> const& __r,
typename enable_if<is_convertible<_Yp*, _Tp*>::value, __nat*>::type)
_NOEXCEPT
: __ptr_(__r.__ptr_),
__cntrl_(__r.__cntrl_)
{
if (__cntrl_)
__cntrl_->__add_weak();
}
// weak_ptr析构器
template<class _Tp>
weak_ptr<_Tp>::~weak_ptr()
{
if (__cntrl_)
__cntrl_->__release_weak();
}
参考链接