问题代码
#include <iostream>
#include <memory>
using namespace std;
// 智能指针测试类
class A
{
public:
A():mptr(new int)
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
delete mptr;
mptr = nullptr;
}
private:
int *mptr;
};
int main()
{
A *p = new A(); // 裸指针指向堆上的对象
shared_ptr<A> ptr1(p);// 用shared_ptr智能指针管理指针p指向的对象
shared_ptr<A> ptr2(p);// 用shared_ptr智能指针管理指针p指向的对象
// 下面两次打印都是1,因此同一个new A()被析构两次,逻辑错误
cout << ptr1.use_count() << endl;
cout << ptr2.use_count() << endl;
return 0;
}
代码输出
A()
1
1
~A()
~A()
代码清单2
#include <iostream>
#include <memory>
using namespace std;
// 智能指针测试类
class A
{
public:
A():mptr(new int)
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
delete mptr;
mptr = nullptr;
}
// A类提供了一个成员方法,返回指向自身对象的shared_ptr智能指针。
shared_ptr<A> getSharedPtr()
{
/*注意:不能直接返回this,在多线程环境下,根本无法获知this指针指向 的对象的生存状态,通过shared_ptr和weak_ptr可以解决多线程访问共享 对象的线程安全问题*/
return shared_ptr<A>(this);
}
private:
int *mptr;
};
int main()
{
shared_ptr<A> ptr1(new A());
shared_ptr<A> ptr2 = ptr1->getSharedPtr();
/* 按原先的想法,上面两个智能指针管理的是同一个A对象资源,但是这里打印都是1 导致出main函数A对象析构两次,析构逻辑有问题*/
cout << ptr1.use_count() << endl;
cout << ptr2.use_count() << endl;
return 0;
}
代码运行结果如下
A()
1
1
~A()
~A()
代码同样有错误,A对象被析构了两次,而且看似两个shared_ptr指向了同一个A对象资源,但是资源计数并没有记录成2,还是1,不正确。
shared_ptr原理
template<class _Ty>
class shared_ptr
: public _Ptr_base<_Ty>
shared_ptr是从_Ptr_base继承而来的,作为派生类,shared_ptr本身没有提供任何成员变量,但是它从基类_Ptr_base继承来了如下成员变量(只罗列部分源码):
template<class _Ty>
class _Ptr_base
{
// base class for shared_ptr and weak_ptr
protected:
void _Decref()
{
// decrement reference count
if (_Rep)
{
_Rep->_Decref();
}
}
void _Decwref()
{
// decrement weak reference count
if (_Rep)
{
_Rep->_Decwref();
}
}
private:
// _Ptr_base的两个成员变量,这里只罗列了_Ptr_base的部分代码
element_type * _Ptr{
nullptr}; // 指向资源的指针
_Ref_count_base * _Rep{
nullptr}; // 指向资源引用计数的指针
};
_Ref_count_base 记录资源的类是怎么定义的呢,如下(只罗列部分源码):增减引用计数时是线程安全的操作
class __declspec(novtable) _Ref_count_base
{
// common code for reference counting
private:
/* _Uses记录了资源的引用计数,也就是引用资源的shared_ptr 的个数;_Weaks记录了weak_ptr的个数,相当于资源观察者的 个数,都是定义成基于CAS操作的原子类型,增减引用计数时是线程安全的操作 */
_Atomic_counter_t _Uses;
_Atomic_counter_t _Weaks;
}
也就是说,当我们定义一个shared_ptr< int > ptr(new int)的智能指针对象时,该智能指针对象本身的内存是8个字节,如下图所示
那么把智能指针管理的外部资源以及引用计数资源都画出来的话,就是如下图的展示
当做这样的操作时
shared_ptr<int> ptr1(new int);
shared_ptr<int> ptr2(ptr1);
cout<<ptr1.use_count()<<endl;
cout<<ptr2.use_count()<<endl;
这段代码没有任何问题,ptr1和ptr2管理了同一个资源,引用计数打印出来的都是2,出函数作用域依次析构,最终new int资源只释放一次,逻辑正确!这是因为shared_ptr ptr2(ptr1)调用了shared_ptr的拷贝构造函数(源码可以自己查看下),只是做了资源的引用计数的改变,没有额外分配其它资源,如下图所示:
但是当做如下操作时
int *p = new int;
shared_ptr<int> ptr1(p);
shared_ptr<int> ptr2(p);
cout<<ptr1.use_count()<<endl;
cout<<ptr2.use_count()<<endl;
这段代码就有问题了,因为shared_ptr ptr1( p )和shared_ptr ptr2( p )都调用了shared_ptr的构造函数,在它的构造函数中,都重新开辟了引用计数的资源,导致ptr1和ptr2都记录了一次new int的引用计数,都是1,析构的时候它俩都去释放内存资源,导致释放逻辑错误,如下图所示:
上面两个代码段,分别是shared_ptr的构造函数和拷贝构造函数做的事情,导致虽然都是指向同一个new int资源,但是对于引用计数对象的管理方式,这两个函数是不一样的,构造函数是新分配引用计数对象,拷贝构造函数只做引用计数增减。
相信说到这里,大家知道最开始的两个代码清单上的代码为什么出错了吧,因为每次调用的都是shared_ptr的构造函数,虽然大家管理的资源都是一样的,_Ptr都是指向同一个堆内存,但是_Rep却指向了不同的引用计数对象,并且都记录引用计数是1,出作用域都去析构,导致问题发生!
问题修改
问题1
int main()
{
A *p = new A(); // 裸指针指向堆上的对象
shared_ptr<A> ptr1(p);// 用shared_ptr智能指针管理指针p指向的对象
shared_ptr<A> ptr2(ptr1);// 用ptr1拷贝构造ptr2
// 下面两次打印都是2,最终随着ptr1和ptr2析构,资源只释放一次,正确!
cout << ptr1.use_count() << endl;
cout << ptr2.use_count() << endl;
return 0;
}
问题2代码修改 enable_shared_from_this和shared_from_this
那么清单2代码怎么修改呢?注意我们有时候想在类里面提供一些方法,返回当前对象的一个shared_ptr强智能指针,做参数传递使用(多线程编程中经常会用到)。
首先肯定不能像上面代码清单2那样写return shared_ptr< A > ( this ) ,这会调用shared_ptr智能指针的构造函数,对this指针指向的对象,又建立了一份引用计数对象,加上main函数中的shared_ptr< A > ptr1(new A());已经对这个A对象建立的引用计数对象,又成了两个引用计数对象,对同一个资源都记录了引用计数,为1,最终两次析构对象释放内存,错误!
那如果一个类要提供一个函数接口,返回一个指向当前对象的shared_ptr智能指针怎么办?方法就是继承enable_shared_from_this类,然后通过调用从基类继承来的shared_from_this()方法返回指向同一个资源对象的智能指针shared_ptr。
#include <iostream>
using namespace std;
// 智能指针测试类,继承enable_shared_from_this类
class A : public enable_shared_from_this<A>
{
public:
A() :mptr(new int)
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
delete mptr;
mptr = nullptr;
}
// A类提供了一个成员方法,返回指向自身对象的shared_ptr智能指针
shared_ptr<A> getSharedPtr()
{
/*通过调用基类的shared_from_this方法得到一个指向当前对象的 智能指针*/
return shared_from_this();
}
private:
int *mptr;
};
enable_shared_from_this 源码实现
一个类继承enable_shared_from_this会怎么样?看看enable_shared_from_this基类的成员变量有什么,如下:
template<class T> class enable_shared_from_this
{
protected:
enable_shared_from_this() BOOST_NOEXCEPT
{
}
enable_shared_from_this(enable_shared_from_this const &) BOOST_NOEXCEPT
{
}
enable_shared_from_this & operator=(enable_shared_from_this const &) BOOST_NOEXCEPT
{
return *this;
}
~enable_shared_from_this() BOOST_NOEXCEPT // ~weak_ptr<T> newer throws, so this call also must not throw
{
}
public:
shared_ptr<T> shared_from_this()
{
shared_ptr<T> p( weak_this_ );
BOOST_ASSERT( p.get() == this );
return p;
}
shared_ptr<T const> shared_from_this() const
{
shared_ptr<T const> p( weak_this_ );
BOOST_ASSERT( p.get() == this );
return p;
}
public: // actually private, but avoids compiler template friendship issues
// Note: invoked automatically by shared_ptr; do not call
template<class X, class Y> void _internal_accept_owner( shared_ptr<X> const * ppx, Y * py ) const
{
if( weak_this_.expired() )
{
weak_this_ = shared_ptr<T>( *ppx, py );
}
}
private:
mutable weak_ptr<T> weak_this_;
};
boost::enable_shared_from_this
也就是说,如果一个类继承了enable_shared_from_this,那么它产生的对象就会从基类enable_shared_from_this继承一个成员变量_Wptr,当定义第一个智能指针对象的时候shared_ptr< A > ptr1(new A()),调用shared_ptr的普通构造函数,就会初始化A对象的成员变量_Wptr,作为观察A对象资源的一个弱智能指针观察者。
- enable_shared_from_this模板类提供两个public属性的shared_from_this成员函数。这两个函数内部会通过weak_this_(weak_ptr类型)成员来创建shared_ptr。
- _internal_accept_owner函数不能手动调用,这个函数会被shared_ptr自动调用,该函数是用来初始化唯一的成员变量weak_this_。
- 根据对象生成顺序,先初始化基类enable_shared_from_this,再初始化派生类对象本身。这时对象己经生成,但weak_this_成员还未被初始化,最后应通过shared_ptr sp(new T())等方式调用shared_ptr构造函数(内部会调用_internal_accept_owner)来初始化weak_this_成员。而如果在调用shared_from_this函数之前weak_this_成员未被初始化,则会通过ASSERT报错提示。
一个继承了enable_shared_from_this的类,再用到shared_from_this()方法之前一定是已经被shared_ptr指过了(肯定有一个计数器了),否则在调用shared_from_this函数之前weak_this_成员未被初始化,则会通过ASSERT报错提示
#include <iostream>
#include <memory>
using namespace std;
// 智能指针测试类,继承enable_shared_from_this类
class A : public enable_shared_from_this<A>
{
public:
A() :mptr(new int)
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
delete mptr;
mptr = nullptr;
}
// A类提供了一个成员方法,返回指向自身对象的shared_ptr智能指针
shared_ptr<A> getSharedPtr()
{
/*通过调用基类的shared_from_this方法得到一个指向当前对象的 智能指针*/
return shared_from_this();
}
private:
int *mptr;
};
int main()
{
A* a = new A();
(void)a;
shared_ptr<A> ptr = a->getSharedPtr();
cout << ptr.use_count() << endl;
}
报错
A()
terminate called after throwing an instance of 'std::bad_weak_ptr'
what(): bad_weak_ptr
int main()
{
A* a = new A();
(void)a;
{
shared_ptr<A> ptr(a);
cout << ptr.use_count() << endl;
}
// 到这 ptr析构已经导致a指向的内存delete了,后面的都是未定义行为,在已经回收的内存上搞事情。
shared_ptr<A> ptr1 = a->shared_from_this();
cout << ptr1.use_count() << endl;
}
报错
A()
1
~A()
65537
具体实现
shared_ptr<_Ty> shared_from_this()
{
// return shared_ptr
return (shared_ptr<_Ty>(_Wptr));
}
shared_ptr< _Ty >(_Wptr),说明通过当前A对象的成员变量_Wptr构造一个shared_ptr出来,看看shared_ptr相应的构造函数:
shared_ptr(const weak_ptr<_Ty2>& _Other)
{
// construct shared_ptr object that owns resource *_Other
if (!this->_Construct_from_weak(_Other)) // 从弱智能指针提升一个强智能指针
{
_THROW(bad_weak_ptr{
});
}
}
template<class _Ty2>
bool _Construct_from_weak(const weak_ptr<_Ty2>& _Other)
{
// implement shared_ptr's ctor from weak_ptr, and weak_ptr::lock()
// if通过判断资源的引用计数是否还在,判定对象的存活状态,对象存活,提升成功;
// 对象析构,提升失败!
if (_Other._Rep && _Other._Rep->_Incref_nz())// !!!这里就要保证weak_ptr存在,只有被shared_ptr指过才存在
{
_Ptr = _Other._Ptr;
_Rep = _Other._Rep;
return (true);
}
return (false);
}
综上所说,所有过程都没有再使用shared_ptr的普通构造函数,没有在产生额外的引用计数对象,不会存在把一个内存资源,进行多次计数的过程;更关键的是,通过weak_ptr到shared_ptr的提升,还可以在多线程环境中判断对象是否存活或者已经析构释放,在多线程环境中是很安全的,通过this裸指针进行构造shared_ptr,不仅仅资源会多次释放,而且在多线程环境中也不确定this指向的对象是否还存活。
最终代码清单2修改如下:
#include <iostream>
using namespace std;
// 智能指针测试类,继承enable_shared_from_this类
class A : public enable_shared_from_this<A>
{
public:
A() :mptr(new int)
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
delete mptr;
mptr = nullptr;
}
// A类提供了一个成员方法,返回指向自身对象的shared_ptr智能指针
shared_ptr<A> getSharedPtr()
{
/*通过调用基类的shared_from_this方法得到一个指向当前对象的 智能指针*/
return shared_from_this();
}
private:
int *mptr;
};
int main()
{
shared_ptr<A> ptr1(new A());
shared_ptr<A> ptr2 = ptr1->getSharedPtr();
// 引用计数打印为2
cout << ptr1.use_count() << endl;
cout << ptr2.use_count() << endl;
return 0;
}
结果如下
A()
2
2
~A()
常用场景
1 延长生命周期
struct A
{
X* x;
};
void A::thread1()
{
while(1)
{
x->fun();
}
}
void A::thread2()
{
x = nullptr;
}
上述写法肯定是不安全的,多线程操作会让x不知道何时nullptr;
struct A
{
shared_ptr<X> x;
};
void A::thread1()
{
while(1)
{
x->fun();
}
}
void A::thread2()
{
x = nullptr;
}
引入了智能指针同样不能解决这个问题。
struct A
{
shared_ptr<X> x;
};
void A::thread1()
{
while(1)
{
auto tmp = x;//延长生命周期
if (!tmp) break;
tmp->fun();
}
}
void A::thread2()
{
x = nullptr;
}
通过新的tmp智能指针延长生命周期,x的析构不一定在x=nullptr触发,也可能是tmp作用域结束后
2 不确定的生命周期
queuue.post_task 或者 x.on_message 这些明显异步或者其他线程的,传入weak_ptr,因为不知道哪个时候a还在不在。
a->on_xxx 这种自己调自己的可以直接传入强指针,执行的时候a一定在
shared_ptr a = make_shared<A>();
weak_ptr<A> weak_a = a;
queuue.post_task([weak_a]()
{
shared_ptr<A> strong_a = weak_a.lock();
if (!strong_a) return;
// ... 之后的逻辑
}
x.on_message([weak_a]()
{
shared_ptr<A> strong_a = weak_a.lock();
if (!strong_a) return;
// ... 之后的逻辑
}
);
auto self = a.shared_from_this();
a->on_xxx([self]()
{
self->fun();
}
);
所以智能指针尽量传值,而不是传地址
传地址效率高(一点),但是引用计数并没有增加,所以并不安全,引用计数没有增加代表这a可能会被外边的值置为空,没有起到延长生命周期的作用
void fun1(shared_ptr<A> a)
{
// 判断a非空后,可以尽情的使用a,占用了一个引用计数,在返回前a都是有效的
cout << a.use_count()<< endl;
}
void fun2(shared_ptr<A>& a)
{
//传入了一个智能指针的地址
//引用计数并没有增加,所以并不安全,引用计数没有增加代表这a可能会被外边的值置为空,没有起到管理生命周期的作用
cout << a.use_count()<< endl;
}
int main()
{
A* a = new A();
shared_ptr<A> ptr(a);
fun1(ptr);
fun2(ptr);
}
输出
A()
2
1
~A()
总结
- shared_ptr内部计数器是原子变量,增减引用计数时是线程安全的操作
- 一个函数接口,返回一个指向当前对象的shared_ptr智能指针,方法就是继承enable_shared_from_this类,然后通过调用从基类继承来的shared_from_this()方法返回指向同一个资源对象的智能指针shared_ptr
- 一个继承了enable_shared_from_this的类,再用到shared_from_this()方法之前一定是已经被shared_ptr指过了(肯定有一个计数器了,使得enable_shared_from_this类内的weak_ptr初始化),否则如果在调用**shared_from_this函数(返回weak_ptr强转之后的shared_ptr)**之前weak_this_成员未被初始化,则会通过ASSERT报错提示
文章评论