RAII技术与智能指针
RAII技术与智能指针
RAII技术
什么是RAII技术
RAII,即Resource Acquisition Is Initialization,“资源获取就是初始化”,是C++语言的一种管理资源、避免泄漏的一种方法。
是一种资源管理技术,利用对象的生命周期管理程序的资源(如内存,文件句柄,锁等)的技术,因为对象在离开作用域的时候,会自动调用析构函数。
实现原理
利用对象的生命周期管理资源,在对象离开作用域时,会自动调用析构函数。
特征
- 在构造时初始化资源,或叫做托管资源。
- 在析构时释放资源
- 不允许赋值或复制(这里指的是对象语义)
- 提供若干访问资源的方法
作用
管理单个对象
值语义与对象语义的区分
-
值语义:可以进行复制与赋值。
-
对象语义:不能进行复制与赋值,一般通过两种方法来实现
- 将拷贝构造函数和赋值运算符函数私有化
- 将拷贝构造函数和赋值运算符函数delete
RAII代码格式
#include <iostream>
//RAII:Resource Acquisition Is Initialization
using std::endl;
using std::cout;
template <typename T>
class RAII {
public:
//通过构造函数托管资源
RAII(T *pData)
:_pData(pData) {
cout << "RAII(T *)" << endl;
}
~RAII() {
if(_pData) {
delete _pData;
_pData = nullptr;
}
cout << "~RAII()" << endl;
}
//访问资源的方法
//定义->运算符函数
T *operator->() {
return _pData;
}
//定义*运算符函数
T &operator*() {
return *_pData;
}
T *get() const {
return _pData;
}
void reset(T *pData) {
if(_pData) {
delete _pData;
_pData = nullptr;
}
_pData = pData;
}
//不提供拷贝构造函数,不允许赋值
RAII(const RAII &rhs) = delete;
//不提供赋值运算符函数,不允许复制
RAII &operator=(const RAII &rhs) = delete;
private:
T *_pData;
};
测试代码(Computer类的实现)
class Computer {
public:
Computer(const char *brand, double price)
:_brand(new char[strlen(brand) + 1]()),
_price(price) {
strcpy(_brand, brand);
cout << "Computer(const char *, double)" << endl;
}
Computer(const Computer &rhs)
:_brand(new char[strlen(rhs._brand) + 1]()),
_price(rhs._price)
{
strcpy(_brand, rhs._brand);
cout << "Computer(const Computer &rhs)" << endl;
}
Computer(Computer &&rhs)
:_brand(rhs._brand),
_price(rhs._price) {
rhs._brand = nullptr;
cout << "Computer(Computer&&)" << endl;
}
Computer & operator=(Computer && rhs) {
cout << "Computer & operator=(Computer &&)" << endl;
if(this != &rhs) {
delete [] _brand;
_brand = rhs._brand;
_price = rhs._price;
rhs._brand = nullptr;
rhs._price = 0;
}
return *this;
}
~Computer() {
delete [] _brand;
cout << "~Computer()" << endl;
}
Computer &operator = (const Computer &rhs) {
if(this != &rhs) { //自复制
delete [] _brand;
_brand = nullptr;
_brand = new char [strlen(rhs._brand) + 1]();
_price = rhs._price;
strcpy(_brand, rhs._brand);
}
return *this;
}
void print() const {
cout << "(" << _brand << "," << _price << ")" << endl;
}
private:
char *_brand;
double _price;
};
void test() {
RAII<Computer> pC(new Computer("lenovo", 8000));
pC->print();
pC.get()->print();
}
int main()
{
test();
return 0;
}
Computer(const char *, double)
RAII(T *)
(lenovo,8000)
(lenovo,8000)
~Computer()
~RAII()
利用RAII技术托管一个Computer对象资源,根据RAII特征
在构造时初始化Computer资源,在析构时释放Computer资源(这里很明显没有调用Computer的析构函数但依旧能够执行),提供了访问资源的方法如get()
我们再接着来验证是否是RAII进行了资源的管理
void test2() {
Computer *pC = new Computer("Huawei mate14", 6000);
}
int main()
{
test2();
return 0;
}
Computer(const char *, double)
发现,当没有调用delete去主动释放对象内存时,是不会调用析构函数的,导致对象中数据成员申请的空间没有得到释放,造成了内存泄漏
void test3() {
Computer *pC = new Computer("Macbook pro", 12000);
RAII<Computer> pR(pC);
pR->print();
pR.get()->print();
}
int main()
{
test3();
Computer(const char *, double)
RAII(T *)
(Macbook pro,12000)
(Macbook pro,12000)
~Computer()
~RAII()
发现,不管是临时对象还是初始化过的对象,在生命周期结束时都会清理掉内存。
结论
RAII技术提供了一种自动回收内存的机制,很好的管理了对象
智能指针
C++在管理内存方面一直都是个大问题,程序员在申请内存时不能准确地保证释放掉这块内存,造成内存泄漏是很正常的事,也是很可怕的事,为此C++标准引入了一种管理对象的机制,能达到自动内存回收的效果,即智能指针。RAII技术就是智能指针的雏形。
auto_ptr
auto_ptr介绍
auto_ptr是一个智能指针(smart pointer),它管理通过new表达式获得的对象,并在auto_ptr其自身被销毁时删除该对象。它可用于将动态分配的对象的所有权传递给函数以及从函数返回动态分配的对象。
auto_ptr的特点
复制auto_ptr对象将复制指针并将所有权转移到目标对象:auto_ptr对象的复制构造和复制赋值函数都会修改它们的传递参数,并且“复制”不等于原来的值。
意思是:经过auto_ptr的拷贝构造函数和赋值运算符函数作用到另一个auto_ptr对象后,当前auto_ptr所管理的内存对象失效了。
Obj obj; auto_ptr<Obj> pa(obj); auto_ptr<Obj> pb(pa);或 pb = pa; //之后pa失效了
因为其这种特点,所以在C++17之后就被弃用了。
auto_ptr的成员函数
constructor
从指向要管理的对象的指针构造auto_ptr对象。
//使用指针p构造auto_ptr对象
auto_ptr( X* p = 0 ) throw();
//使用r.release()中的指针构造auto_ptr对象。r.release()被调用以获得对象的所有权。
auto_ptr( auto_ptr& r ) throw();
operator=
//将托管对象替换为r所管理的对象,即将r管理的资源的管理权转移给了新的auto_ptr对象
//底部调用了reset(r.release())
auto_ptr& operator=( auto_ptr& r ) throw();
operator*
//获取所托管/管理的对象
//解引用一个指向托管对象的指针
T& operator*() const throw();
operator->
//获取所管理的对象
//解引用一个指向托管对象的指针
T* operator->() const throw();
get
//返回由*this持有的指针
T* get() const throw();
reset
//用p替换持有的指针。如果当前持有的指针不是空指针,则调用delete get()
//参数是需要管理对象的指针
void reset( T* p = 0 ) throw();
release
//Releases the held pointer. After the call *this holds the null pointer.
//释放所持有的指针。在此之后*this持有的是一个空指针。
T* release() throw();
auto_ptr源码解析
//只展示重要部分
template <class _Tp> class auto_ptr {
private:
//有一个_Tp类型的指针变量
_Tp* _M_ptr;
public:
typedef _Tp element_type;
//构造函数
auto_ptr(_Tp* __p = 0) : _M_ptr(__p) {}
//拷贝构造函数
auto_ptr(auto_ptr& __a) : _M_ptr(__a.release()) {}
//赋值运算符函数
auto_ptr& operator=(auto_ptr& __a) __STL_NOTHROW {
if (&__a != this) {
delete _M_ptr;
_M_ptr = __a.release();
}
return *this;
}
//析构函数
~auto_ptr() { delete _M_ptr; }
//*运算符函数
_Tp& operator*() const __STL_NOTHROW {
return *_M_ptr;
}
//->运算符函数
_Tp* operator->() const __STL_NOTHROW {
return _M_ptr;
}
//get函数
_Tp* get() const __STL_NOTHROW {
return _M_ptr;
}
//release函数
_Tp* release() __STL_NOTHROW {
_Tp* __tmp = _M_ptr;
_M_ptr = 0;
return __tmp;
}
//reset函数
void reset(_Tp* __p = 0) __STL_NOTHROW {
if (__p != _M_ptr) {
delete _M_ptr;
_M_ptr = __p;
}
}
从源码上来看,auto_ptr的核心操作是进行了移动构造,如operator=和release和reset函数所示,都会将当前对象销毁然后获取传来的对象,然后让被拷贝的对象指针指向null。
所以也验证了auto_ptr的特点:在进行复制的时候会发生托管对象所有权的转移。
auto_ptr的例子
我们通过一个例子来查看auto_ptr在使用中的效果
//用的还是上面的Computer类来进行测试,这里就不明确写了
void test1() {
Computer *c = new Computer("lenovo", 8000);
std::auto_ptr<Computer> pc(c);
pc->print();
}
void test2() {
Computer *c = new Computer("lenovo", 8000);
Computer *c2 = new Computer("huawei", 4500);
std::auto_ptr<Computer> pc(c);
std::auto_ptr<Computer> pc2(c2);
pc = pc2;
pc->print();
/* pc2->print(); */
}
int main() {
//test1();
return 0;
}
下为程序运行效果,发现test1()单独使用auto_ptr去托管一个对象资源是挺正常的
test2()则是当进行 pc=pc2时,pc2所托管的对象资源转交给pc管理,此时pc管理的原对象资源则被释放
当我们打开pc2—>print()
的注释时,此时的运行结果则会出现段错误,是因为pc2此时托管的对象是null
同理拷贝构造也是一样,即auto_ptr<Computer> pc2 = pc;
有兴趣的自己测一下,这里不再测试。
//test1()
Computer(const char *, double)
(lenovo,8000)
~Computer()
//test2()
Computer(const char *, double)
Computer(const char *, double)
~Computer()
(huawei,4500)
~Computer()
//打开pc2->print()的注释
Computer(const char *, double)
Computer(const char *, double)
~Computer()
(huawei,4500)
Segmentation fault (core dumped)
unique_ptr
unique_ptr介绍
unique_ptr是一个智能指针,它通过指针拥有和管理另一个对象,并在unique_ptr超出作用域时处理该对象(即生命周期结束)。
当发生以下情况之一时,使用关联的删除器释放该对象:
- 正在管理的unique_ptr对象将被销毁
- 通过操作符或resset()为管理对象unique_ptr分配另一个指针
- 通过调用get_deleter()(ptr),使用潜在的用户提供的删除器来释放对象。默认的删除器使用delete操作符,该操作符会销毁对象并释放内存。
unique_ptr也可以不拥有任何对象,在这种情况下它被称为空对象。
unique_ptr的特点
之前的auto_ptr能进行复制操作,但是会出现所有权的转移,这里在用的时候经常会出现问题。而unique_ptr在这里比auto_ptr安全多了,明确表明是独享所有权的智能指针,所以不能进行复制与赋值。
- 是一个独享所有权的智能指针
- 能够访问资源
- 不能够进行赋值或复制
- 具有移动语义,可以作为容器的元素
//针对于第4点做出解释
//正确写法
Computer c = new Computer("lenovo", 8000);
unique_ptr<Computer> up(c);
vector<unique_ptr<Computer>> computers; //容器中存放的是智能指针
computers.push_back(std::move(up));
//错误写法
Computer c = new Computer("lenovo", 8000);
unique_ptr<Computer> up(c);
vector<unique_ptr<Computer>> computers; //容器中存放的是智能指针
computers.push_back(up); //error,传递左值,会调用拷贝构造函数
unique_ptr的成员函数
这里只先说明这一类,后续在源码解析的部分说明
constructor
members of the primary template, unique_ptr<T>
(1)
unique_ptr();
unique_ptr( nullptr_t );
(2)
unique_ptr( pointer p );
(3)
unique_ptr( pointer p, /* see below */ d1 );
(4)
unique_ptr( pointer p, /* see below */ d2 );
(5)
unique_ptr( unique_ptr&& u );
(6)
template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u );
(7) (removed in C++17)
template< class U >
unique_ptr( std::auto_ptr<U>&& u );
members of the specialization for arrays, unique_ptr<T[]>
(1)
constexpr unique_ptr() noexcept;
constexpr unique_ptr( nullptr_t ) noexcept;
(2) (until C++17)
explicit unique_ptr( pointer p ) noexcept;
(2) (since C++17)
template<class U> explicit unique_ptr( U p ) noexcept;
(3) (until C++17)
unique_ptr( pointer p, /* see below */ d1 ) noexcept;
(3) (since C++17)
template<class U> unique_ptr( U p, /* see below */ d1 ) noexcept;
(4) (until C++17)
unique_ptr( pointer p, /* see below */ d2 ) noexcept;
(4) (since C++17)
template<class U> unique_ptr( U p, /* see below */ d2 ) noexcept;
(5)
unique_ptr( unique_ptr&& u ) noexcept;
(6) (since C++17)
template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept;
通过截取unique_ptr的构造函数声明来看,unique_ptr直接没有提供拷贝构造函数,从源头上避免了资源的托管权的转移。
例子(两个例子:对象语义和移动拷贝构造)
(1)我们通过一个例子来验证unique_ptr表达的是对象语义(不能够进行赋值和复制)
//这里我们还是用Computer类进行测试
void test2() {
Computer *c = new Computer("lenovo", 8000);
Computer *c2 = new Computer("huawei", 4500);
std::unique_ptr<Computer> pc(c);
std::unique_ptr<Computer> pc2(c2);
std::unique_ptr<Computer> pc3 = pc; //error
pc = pc2; //error
pc->print();
pc2->print();
}
这里当我们进行编译的时候会发现程序报错了,这里编译器给出原因,原因是unique_ptr的拷贝构造和赋值运算符函数都被删除掉了。
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
(2)但是,unique_ptr还是提供了移动拷贝构造,使用移动拷贝构造可以实现对象资源的转移
void test3() {
Computer *c = new Computer("lenovo", 8000);
Computer *c2 = new Computer("huawei", 4500);
std::unique_ptr<Computer> pc(c);
std::unique_ptr<Computer> pc2(c2);
pc = std::move(pc2); //将pc2转换成了右值,pc2托管的资源就变成了空指针
pc->print();
/* pc2->print(); */
}
Computer(const char *, double)
Computer(const char *, double)
~Computer()
(huawei,4500)
~Computer()
此时,若把pc2->print()的注释打开同样也会出现段错误
其他成员函数,挑几个在源码解析部分说明
unique_ptr源码解析
在Linux平台下,c++的源码的头文件在 /usr/include/c++/…
特别注意:这里只是提供了该类的大概函数的源码实现,注释只是个人的思考,逻辑上有些跳跃的地方,并且有些地方写的不对,能看懂的读着可以自己去理解源码,这里只是提供了一个思考的方向。
这里我们为了方便阅读,将各个模块分开来看,整体结构保持不变
注:可能删除了一些不方便阅读的部分,如模板的定义,保证了函数的可阅读性
//在unique_ptr的源码挑选出几个重要的拿出来说明
//解释_Dp就是默认销毁的方式,我们这里不用管,只需要观察_Tp的部分即可
template <typename _Tp, typename _Dp = default_delete<_Tp>>
class unique_ptr
{
template <class _Up>
using _DeleterConstraint = typename __uniq_ptr_impl<_Tp, _Up>::_DeleterConstraint::type;
__uniq_ptr_impl<_Tp, _Dp> _M_t;
public:
using pointer = typename __uniq_ptr_impl<_Tp, _Dp>::pointer; //这个pointer就是_uniq_ptr_impl中的 //pointer,说白了就是所托管对象的指针
using element_type = _Tp;
using deleter_type = _Dp;
Constructors
对于源码的解析,将说明放在代码部分。
// Constructors.
/// Default constructor, creates a unique_ptr that owns nothing.
unique_ptr() : _M_t() { }
//就是一个简单的构造函数,将要托管对象的指针传进来
unique_ptr(pointer __p) : _M_t(__p) { }
/// Move constructor.
//移动构造函数部分,这里主要注意release()函数
//见下面release()的实现
unique_ptr(unique_ptr&& __u) noexcept
: _M_t(__u.release(), std::forward<deleter_type>(__u.get_deleter())) { }
/// Destructor, invokes the deleter if the stored pointer is not null.
//析构函数,将本auto_ptr所托管对象的指针复制给__ptr,注意__ptr是一个引用
//若不为空对象,则调用了get_deleter()销毁对象
//见下方get_deleter()
~unique_ptr() noexcept {
auto& __ptr = _M_t._M_ptr();
if (__ptr != nullptr)
get_deleter()(__ptr);
__ptr = pointer(); //这里的pointer()是typename __uniq_ptr_impl<_Tp, _Dp>::pointer;的别名, //相当于调用了
}
Assignment(operator =)
// Assignment.
//Invokes the deleter first if this object owns a pointer
//如果该对象拥有指针,则首先调用删除器
//注意这里是移动赋值运算符,接受的是一个右值,不要搞混了
//这里的几个函数,可以向下看具体的实现,下面提供的有
unique_ptr& operator=(unique_ptr&& __u) noexcept {
reset(__u.release());
get_deleter() = std::forward<deleter_type>(__u.get_deleter());
return *this;
}
observers
// Observers.
/// Dereference the stored pointer.
//解引用这个这隔存储对象的指针
//发现,返回的是*get()
//返回的是对所托管对象的指针的解引用,即获得托管对象本身
typename add_lvalue_reference<element_type>::type
operator*() const {
__glibcxx_assert(get() != pointer());
return *get();
}
/// Return the stored pointer.
//返回的是get()
//返回的是所托管的对象
pointer operator->() const noexcept {
_GLIBCXX_DEBUG_PEDASSERT(get() != pointer());
return get();
}
/// Return the stored pointer.
//此时我们发现get()函数内部调用的是_M_t对象中的_M_ptr()函数
//这里我们再做一个源码跳转,具体见__uniq_ptr_impl类
//返回所托管对象的指针
pointer get() const noexcept {
return _M_t._M_ptr(); //即这里调用的_M_t.M_ptr()实际上返回的就是托管对象的指针
}
// Return a reference to the stored deleter.
deleter_type&
get_deleter() noexcept
{ return _M_t._M_deleter(); }
modifiers
// Modifiers.
/// Release ownership of any stored pointer.
//释放托管对象指针的拥有权
//返回一个指向托管对象的指针并释放其所有权
//这里只是没有了托管对象的所有权,但托管的对象还在(前提是在声明周期运行之内)
pointer release() noexcept {
pointer __p = get();
_M_t._M_ptr() = pointer();
return __p;
}
/** @brief Replace the stored pointer.
*
* @param __p The new pointer to store.
*
* The deleter will be invoked if a pointer is already owned.
*/
//replaces the managed object
//取代所管理的对象
//同时会把托管的对象资源给释放掉
void reset(pointer __p = pointer()) noexcept
{
using std::swap;
swap(_M_t._M_ptr(), __p);
if (__p != pointer())
get_deleter()(__p);
}
/// Exchange the pointer and deleter with another object.
void swap(unique_ptr& __u) noexcept {
using std::swap;
swap(_M_t, __u._M_t);
}
// Disable copy from lvalue.
//果然,unique_ptr在实现中将拷贝构造函数和赋值运算符函数给删除了
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
};
__uniq_ptr_impl类
template <typename _Tp, typename _Dp>
class __uniq_ptr_impl
{
template <typename _Up, typename _Ep, typename = void>
struct _Ptr
{
using type = _Up*;
};
template <typename _Up, typename _Ep>
struct
_Ptr<_Up, _Ep, __void_t<typename remove_reference<_Ep>::type::pointer>>
{
using type = typename remove_reference<_Ep>::type::pointer;
};
public:
using _DeleterConstraint = enable_if<
__and_<__not_<is_pointer<_Dp>>,
is_default_constructible<_Dp>>::value>;
using pointer = typename _Ptr<_Tp, _Dp>::type; //这里pointer是remove_reference类中的pointer对象
__uniq_ptr_impl() = default;
__uniq_ptr_impl(pointer __p) : _M_t() { _M_ptr() = __p; }//发现这里通过unique_ptr托管的类型指针就是这里的_p然后赋值给_M_ptr()函数,即通过返回引用类型,实际上是一个pointer,即
template<typename _Del>
__uniq_ptr_impl(pointer __p, _Del&& __d)
: _M_t(__p, std::forward<_Del>(__d)) { }
pointer& _M_ptr() { return std::get<0>(_M_t); } //即这里就是返回托管对象的指针
pointer _M_ptr() const { return std::get<0>(_M_t); }
_Dp& _M_deleter() { return std::get<1>(_M_t); }
const _Dp& _M_deleter() const { return std::get<1>(_M_t); }
private:
tuple<pointer, _Dp> _M_t; //发现_M_t就是一个元组,之前说过_Dp部分先不看,那么就是为了实现pointer的功能
//而这个pointer就是一个_Ptr(_Tp, _Dp)::type的别名
};
unique_ptr的例子(函数的使用)
之前在unique_ptr的成员函数那里说明了移动构造函数的使用
这里我们再举几个例子
release()函数的使用
这里我们发现,release函数只是让该unique_ptr智能指针对象失去了c对象的托管权,c对象本身并没有释放,通过unique_ptr<Computer> pc3(c);
的重新托管可以发现,c对象还是存在的,并且还能使用该对象
void test3() {
Computer *c = new Computer("lenovo", 8000);
Computer *c2 = new Computer("huawei", 4500);
std::unique_ptr<Computer> pc(c);
std::unique_ptr<Computer> pc2(c2);
pc.release();
std::unique_ptr<Computer> pc3(c);
pc3->print();
}
Computer(const char *, double)
Computer(const char *, double)
(lenovo,8000)
~Computer()
~Computer()
reset()函数的使用
这里我们发现,pc托管的c对象,在pc使用reset()函数之后,c对象也得到了释放;
并且用pc3智能指针重新托管时发现,在使用时是错误的数据,并且释放时也显示了double free的问题,即reset()函数释放了一次,pc3托管那片内存区域之后,生命周期结束又释放了一次
void test4() {
Computer *c = new Computer("lenovo", 8000);
Computer *c2 = new Computer("huawei", 4500);
std::unique_ptr<Computer> pc(c);
std::unique_ptr<Computer> pc2(c2);
pc.reset();
std::unique_ptr<Computer> pc3(c);
pc3->print();
}
Computer(const char *, double)
Computer(const char *, double)
~Computer()
(,4.6489e-310)
free(): double free detected in tcache 2
Aborted (core dumped)
swap()函数的使用
发现使用swap()函数之后,pc和pc2两个智能指针托管的对象交换了
void test5() {
Computer *c = new Computer("lenovo", 8000);
Computer *c2 = new Computer("huawei", 4500);
std::unique_ptr<Computer> pc(c);
std::unique_ptr<Computer> pc2(c2);
cout << "before swap" << endl;
pc->print();
pc2->print();
cout << "after swap" << endl;
pc.swap(pc2);
pc->print();
pc2->print();
}
Computer(const char *, double)
Computer(const char *, double)
before swap
(lenovo,8000)
(huawei,4500)
after swap
(huawei,4500)
(lenovo,8000)
~Computer()
~Computer()
shared_ptr(重要)
上述的两种智能指针现都已弃用了,缺点都很明显,auto_ptr使用时会照成使用权的转移;unique_ptr是独享对象所有权。
而在shared_ptr中改善了这两种情况
shared_ptr介绍
shared_ptr是一个智能指针,通过指针保留对象的共享所有权。多个shared_ptr对象可能拥有通过一个对象。当发生以下任一情况时,对象将被销毁并释放其内存
- 最后剩余的shared_ptr拥有该对象的对象被销毁。
- shared_ptr通过operator=或reset()为最后一个拥有该对象的对象分配另一个指针。
shared_ptr特点
- shared_ptr是一种共享所有权的智能指针
- 能够访问资源
- 是一种**强引用**类型的指针指针
- 可以进行复制或赋值
- 通过引用计数来完成的
- 当进行复制控制操作时,引用计数加1
- 当shared_ptr被销毁时,先将引用计数减1;再去判断引用计数是否为0;如果为0,才真正释放资源
- use_count方法获取引用计数的值
- 通过引用计数来完成的
- 可以进行转移操作
总结来说,shared_ptr智能指针满足了共享所有权的需求,即是shared_ptr采用的思想与之前的写时复制技术类型,浅拷贝+引用计数,user_count函数记录指向一块内存的指针的数目。
shared_ptr的成员函数
在这里我们对于一些常用的函数做一些解释说明,具体的使用可以到shared_ptr的使用例子查看
constructor
//无参构造
constexpr shared_ptr() noexcept;
//有参构造
template< class Y >
explicit shared_ptr( Y* ptr );
//拷贝构造
template< class Y >
shared_ptr( const shared_ptr<Y>& r, element_type* ptr ) noexcept;
//移动拷贝构造
template< class Y >
shared_ptr( shared_ptr<Y>&& r ) noexcept;
destructors
//如果*this拥有一个对象并且它是最后shared_ptr拥有它的对象,则该对象将通过拥有的删除器销毁。
//销毁后,与共享所有权的智能指针*this(如果有)将报告比其先前值小1的use_count()
~shared_ptr();
assign(赋值运算符)
//将托管对象替换为r所管理的对象。
//如果*this已经拥有一个对象,并且它是最后一个拥有该对象的shared_ptr,并且r与*this不相同,则通过拥有的删除器销毁该对象。
template< class Y >
shared_ptr& operator=( const shared_ptr<Y>& r ) noexcept;
template< class Y >
shared_ptr& operator=( shared_ptr<Y>&& r ) noexcept;
reset
std::shared_ptr<T>::reset
/*
将托管对象替换为ptr所指向的对象。
可以提供可选的删除器d,当没有shared_ptr对象拥有新对象时,使用该删除器d销毁新对象。
默认情况下,delete表达式被用作删除器。与所提供的类型相对应的正确的删除表达式总是被选中的,
这就是为什么该函数使用单独的参数Y作为模板实现的原因。
*/
/*
如果*this已经拥有了一个对象,并且它是最后一个拥有该对象的shared_ptr,则通过拥有的删除器销毁该对象。
如果ptr所指的对象已经被拥有了,该函数将导致未定义的行为。
*/
//释放托管对象的所有权(如果有的话)。调用之后,*this不管理任何对象,相当于要().swap(*)
void reset() noexcept;
//将托管对象替换为ptr所指向的对象。Y必须是一个完整类型,并且可以隐式转换为T类型。
template< class Y >
void reset( Y* ptr );
swap
交换*this和r的内容
void swap( shared_ptr& r ) noexcept;
get
//返回存储对象的指针
T* get() const noexcept;
operator* 、 operator->
//解引用存储的指针。如果存储的指针为空,则该行为未定义
//对存储的指针进行解引用,即*get()
T& operator*() const noexcept;
//返回存储的指针,即get()
T* operator->() const noexcept;
use_count
//返回管理当前对象的不同shared_ptr实例(包括这个)的数量,如果没有管理对象,则返回0
long use_count() const noexcept;
operator bool
//检查*this是否存储了一个非空指针,即是否get()!=nullptr
//如果*this存储一个指针,则为True,否则为false
explicit operator bool() const noexcept;
shared_ptr的非成员函数
这里我们简单介绍一个非成员函数
make_shared
make_shared函数是最安全的分配和使用动态内存的标准库函数。
此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。
当要用make_shared时,必须指定想要创建的对象的类型。
//可以看出是构造一个shared_ptr<T>的对象,同时参数传递的是右值
template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );
shared_ptr的源码解析
从shared_ptr的源码上扒下来几个成员函数的实现,中间把妨碍内容的部分做了删减,保留了可阅读部分
对于这部分源码会涉及到底层的一些实现,我们看表明函数含义即可
class __shared_ptr {
..
};
数据成员
using element_type = _Tp; //note: _Tp is type of typename<class _Tp>
element_type* _M_ptr; // Contained pointer.
__shared_count<_Lp> _M_refcount; // Reference counter.
constructor
//无参构造
constexpr __shared_ptr() noexcept
: _M_ptr(0), _M_refcount() { }
//有参构造
template<typename _Yp, typename = _SafeConv<_Yp>>
explicit __shared_ptr(_Yp* __p) : _M_ptr(__p), _M_refcount(__p, typename is_array<_Tp>::type())
{
...
_M_enable_shared_from_this_with(__p);
}
//拷贝构造
template<typename _Yp>
__shared_ptr(const __shared_ptr<_Yp, _Lp>& __r,element_type* __p)
: _M_ptr(__p), _M_refcount(__r._M_refcount) //这里的_M_refcount就是引用计数的意思
{ //并且进行完拷贝构造之后,所有指向这个资源的shared_ptr的的_M_refcount都会加1
/*大概操作
count = __r.count;
this->_M_ptr = __r._M_ptr;
++(*count);
*/
}
__shared_ptr(const __shared_ptr&) noexcept = default;
//移动拷贝构造
__shared_ptr(__shared_ptr&& __r) noexcept
: _M_ptr(__r._M_ptr), _M_refcount() {
_M_refcount._M_swap(__r._M_refcount);
__r._M_ptr = 0;
}
assign
template<typename _Yp>
_Assignable<_Yp> operator=(const __shared_ptr<_Yp, _Lp>& __r) noexcept {
_M_ptr = __r._M_ptr;
_M_refcount = __r._M_refcount;
return *this;
}
//移动赋值运算符函数
__shared_ptr& operator=(__shared_ptr&& __r) noexcept {
__shared_ptr(std::move(__r)).swap(*this);
return *this;
}
reset
void reset() noexcept
{ __shared_ptr().swap(*this); }
template<typename _Yp>
_SafeConv<_Yp> reset(_Yp* __p) { //_Yp must be complete.
// Catch self-reset errors.
__glibcxx_assert(__p == 0 || __p != _M_ptr);
__shared_ptr(__p).swap(*this);
}
template<typename _Yp, typename _Deleter>
_SafeConv<_Yp> reset(_Yp* __p, _Deleter __d)
{ __shared_ptr(__p, std::move(__d)).swap(*this); }
get() bool() use_count() swap()
element_type* get() const noexcept { return _M_ptr; }
explicit operator bool() const // never throws
{ return _M_ptr == 0 ? false : true; }
long use_count() const noexcept { return _M_refcount._M_get_use_count(); }
_Atomic_word _M_use_count; // #shared
long _M_get_use_count() const noexcept { //初始时_M_use_count=1;
return __atomic_load_n(&_M_use_count, __ATOMIC_RELAXED);
}
void swap(__shared_ptr<_Tp, _Lp>& __other) noexcept {
std::swap(_M_ptr, __other._M_ptr);
_M_refcount._M_swap(__other._M_refcount);
}
shared_ptr的使用例子
在这里我们将对shared_ptr做一些简单的运用。
简单运用
void test1() {
Computer *c = new Computer("lenovo", 8000);
shared_ptr<Computer> sp(c);
sp->print();
}
/*
Computer(const char *, double)
(lenovo,8000)
~Computer()
*/
void test2() {
Computer *c = new Computer("lenovo", 8000);
shared_ptr<Computer> sp(c); //构造函数
cout << "sp.use_count() = " << sp.use_count() << endl;
cout << endl;
shared_ptr<Computer> sp2(sp); //拷贝构造
cout << "sp.use_count() = " << sp.use_count() << endl;
cout << "sp2.use_count() = " << sp2.use_count() << endl;
cout << endl;
shared_ptr<Computer> sp3;
sp3 = sp2; //赋值运算符函数
cout << "sp.use_count() = " << sp.use_count() << endl;
cout << "sp2.use_count() = " << sp2.use_count() << endl;
cout << "sp3.use_count() = " << sp3.use_count() << endl;
cout << endl;
shared_ptr<Computer> sp4(std::move(sp)); //移动拷贝构造
cout << "sp.use_count() = " << sp.use_count() << endl;
cout << "sp2.use_count() = " << sp2.use_count() << endl;
cout << "sp3.use_count() = " << sp3.use_count() << endl;
cout << "sp4.use_count() = " << sp4.use_count() << endl;
cout << endl;
shared_ptr<Computer> sp5 = std::move(sp2); //移动赋值
cout << "sp.use_count() = " << sp.use_count() << endl;
cout << "sp2.use_count() = " << sp2.use_count() << endl;
cout << "sp3.use_count() = " << sp3.use_count() << endl;
cout << "sp4.use_count() = " << sp4.use_count() << endl;
cout << "sp5.use_count() = " << sp5.use_count() << endl;
}
/*
Computer(const char *, double)
sp.use_count() = 1
sp.use_count() = 2
sp2.use_count() = 2
sp.use_count() = 3
sp2.use_count() = 3
sp3.use_count() = 3
sp.use_count() = 0
sp2.use_count() = 3
sp3.use_count() = 3
sp4.use_count() = 3
sp.use_count() = 0
sp2.use_count() = 0
sp3.use_count() = 3
sp4.use_count() = 3
sp5.use_count() = 3
~Computer()
*/
从test2()的代码得出,当shared_ptr进行复制控制时会使的托管该资源的数量的shared_ptr个数加1,即use_count()引用计数加一。
但当使用移动语义时会将原托管对象的shared_ptr权力收回。
void test3() {
Computer *c = new Computer("lenovo", 8000);
shared_ptr<Computer> sp(c);
cout << "sp.use_count() = " << sp.use_count() << endl;
sp.reset();
cout << "-----------------" << endl;
}
/*
Computer(const char *, double)
sp.use_count() = 2
sp2.use_count() = 2
-----------------
~Computer()
*/
void test4() {
Computer *c = new Computer("lenovo", 8000);
shared_ptr<Computer> sp(c);
shared_ptr<Computer> sp2 = sp;
cout << "sp.use_count() = " << sp.use_count() << endl;
cout << "sp2.use_count() = " << sp2.use_count() << endl;
sp.reset();
/* sp->print(); */ //error
sp2->print();
cout << "-----------------" << endl;
cout << "sp.use_count() = " << sp.use_count() << endl;
cout << "sp2.use_count() = " << sp2.use_count() << endl;
}
/*
Computer(const char *, double)
sp.use_count() = 2
sp2.use_count() = 2
(lenovo,8000)
-----------------
sp.use_count() = 0
sp2.use_count() = 1
~Computer()
*/
当只有一个shared_ptr对象来托管c资源时,使用reset()后,会立即释放c资源。
当有多个shared_ptr对象来托管c资源时,有shared_ptr使用reset()后,只要还有一个智能指针托管c资源,那么c资源就不会被释放。
void test5() {
Computer *c = new Computer("lenovo", 8000);
Computer *c1 = new Computer("huawei", 6000);
shared_ptr<Computer> sp(c);
shared_ptr<Computer> sp2(c1);
(*sp).print();
(*sp2).print();
sp.swap(sp2);
cout << endl << "after swap" << endl;
(*sp).print();
(*sp2).print();
}
/*
Computer(const char *, double)
Computer(const char *, double)
(lenovo,8000)
(huawei,6000)
after swap
(huawei,6000)
(lenovo,8000)
~Computer()
~Computer()
*/
swap还是交换的功能,没什么好说的
”二龙治水问题“
即用两个shared_ptr初始化时托管同一份资源
//下面出现了double free的问题
void test6() {
Computer *c = new Computer("lenovo", 8000);
shared_ptr<Computer> sp(c); //<-----
cout << "sp.use_count() = " << sp.use_count() << endl;
cout << endl << endl;
shared_ptr<Computer> sp2(c); //<------
cout << "sp.use_count() = " << sp.use_count() << endl;
cout << "sp2.use_count() = " << sp2.use_count() << endl;
(*sp).print();
(*sp2).print();
cout << endl << "-------------" << endl;
}
/*
Computer(const char *, double)
sp.use_count() = 1
sp.use_count() = 1
sp2.use_count() = 1
(lenovo,8000)
(lenovo,8000)
-------------
~Computer()
free(): double free detected in tcache 2
Aborted (core dumped)
*/
为什么会出现double free?。
首先注意,先后使用sp,sp2对同一个资源进行托管,但是引用计数却还是1,这是因为shared_ptr<Computer> sp(c);
这种方式使用的是有参构造的方式,从原码上看在初始化列表里_M_ptr(__p)
意思就是将shared_ptr中的资源指针赋值为资源的地址。
而拷贝控制干了什么事呢,为啥不会出现这种情况?
是因为它们都干了同一件事,在源码中_M_ptr = __r.M_ptr
,意思就是我这个shared_ptr对象的资源指针其实就是你资源指针保存的内容(即托管对象的地址)。但这个跟上面的_M_ptr(__p)
好像没有多大区别
区别在于使用复制控制的方式之后引入计数都会加一,安装正常的来说
Computer *c = new Computer("lenovo", 8000);
Computer *c1 = new Computer("huawei", 6000);
shared_ptr<Computer> sp(c);
shared_ptr<Computer> sp2 = sp;
此时sp和sp2的引用计数都为2,然后程序运行结束了,生命周期也到了终点,此时要弹栈,这里在清理shared_ptr时会进行引用计数–的操作,先弹sp2,发现use_count为2,然后将托管对象的引用计数–,发现不为0,即不进行托管对象的析构操作,然后到sp,sp结束,sp的引用计数–,发现use_count为0了,此时进行托管对象的析构。
Computer *c = new Computer("lenovo", 8000);
Computer *c1 = new Computer("huawei", 6000);
shared_ptr<Computer> sp(c);
shared_ptr<Computer> sp2(c);
这种是进行了什么操作呢,我们直接看过这种是sp的use_count为1,sp2的use_count为1,此时程序结束,弹栈,sp2的use_count–,发现为0,释放托管的对象;然后到sp,发现为0,还有进行释放,此时就出现了double free的现象
shared_ptr的循环引用的问题
shared_ptr是强引用类型的智能指针,在使用的时候会使得引用计数增加。
关于shared_ptr的循环引用的问题是,两个shared_ptr智能指针互指,导致引用计数增加,不能靠对象的销毁使得引用计数变为0,从而导致内存泄漏。
#include <iostream>
#include <memory>
using std::endl;
using std::cout;
using std::shared_ptr;
class B;
class A {
public:
A() {
cout << "A()" << endl;
}
~A() {
cout << "~A()" << endl;
}
//即B *spB 是A的数据成员
shared_ptr<B> spB;
};
class B {
public:
B() {
cout << "B()" << endl;
}
~B() {
cout << "~B()" << endl;
}
shared_ptr<A> spA;
};
void test() {
//循环引用可能导致内存泄漏
shared_ptr<A> pA(new A());
shared_ptr<B> pB(new B());
cout << "pA.use_count() = " << pA.use_count() << endl;
cout << "pB.use_count() = " << pB.use_count() << endl;
cout << endl;
pA->spB = pB; //即让A的中的数据成员spB(spB是一个用来托管B类资源的智能指针)
//来托管pB智能中的资源
pB->spA = pA;
cout << "pA.use_count() = " << pA.use_count() << endl;
cout << "pB.use_count() = " << pB.use_count() << endl;
}
int main() {
test();
return 0;
}
A()
B()
pA.use_count() = 1
pB.use_count() = 1
pA.use_count() = 2
pB.use_count() = 2
通过执行结果来看,当程序的生命周期结束的时候,并没有析构A和B的对象。
这就是循环引用照成的问题
下面我们通过图示的方法来解释
这是最初时的状态
当我们使用pA->spB = pB;
和pB->spA = pA;
时此时的内存图模型为
这时,A对象和B对象都被两个shared_ptr智能指针托管。
A对象是被shared_ptr<A> pA
托管,同时还被B对象的数据成员shared_ptr<A> spA
托管。
同理B对象被shared_ptr<B> pB
托管,同时还被A对象的数据成员shared_ptr<B> spB
托管。
当程序的生命周期结束,发生弹栈操作时,这些引用计数该怎么变化呢?为什么没有析构掉A和B对象?
我们根据入栈的顺序得到弹栈的顺序,来进行程序的讲解
首先先将pB弹栈
接着进行pA的弹栈
此时我们发现,A和B两个对象上的引用计数都还存在,且不为0,这意味着,不会进行对象的析构。也就是A和B对象不会得到释放,这跟我们之前程序的运行结果相同,程序结束后也没有调用A和B的析构函数。
那么如何解决这个问题呢?
利用weak_ptr的弱引用特性。只需将其中一个类的数据类型从shared_ptr改为weak_ptr
class B;
class A {
public:
A() {
cout << "A()" << endl;
}
~A() {
cout << "~A()" << endl;
}
//即B *spB 是A的数据成员
shared_ptr<B> spB;
};
class B {
public:
B() {
cout << "B()" << endl;
}
~B() {
cout << "~B()" << endl;
}
/* shared_ptr<A> spA; */
weak_ptr<A> wpA; //<----------------- 这里换成了weak_ptr
};
void test() {
//循环引用可能导致内存泄漏
shared_ptr<A> pA(new A());
shared_ptr<B> pB(new B());
cout << "pA.use_count() = " << pA.use_count() << endl;
cout << "pB.use_count() = " << pB.use_count() << endl;
cout << endl;
pA->spB = pB; //即让A的中的数据成员spB(spB是一个用来托管B类资源的智能指针)
//来托管pB智能中的资源
/* pB->spA = pA; */
pB->wpA = pA;
cout << "pA.use_count() = " << pA.use_count() << endl;
cout << "pB.use_count() = " << pB.use_count() << endl;
}
B()
A()
pA.use_count() = 1
pB.use_count() = 1
pA.use_count() = 1
pB.use_count() = 2
~A()
~B()
此时发现可以正常的将对象释放掉。
利用图型方式来查看发生了什么
将B类的数据成员从shared_ptr<A> spA
转换成weak_ptr<A> wpA
之后的效果
然后再来看看当程序的生命周期结束时的过程。
我们先来看看pB的消除
再来看看pA的消除
我们将pA消除带来的效果分为两步
总结:利用weak_ptr搭配shared_ptr的方式可以解决循环引用的问题。
weak_ptr
weak_ptr介绍
template< class T > class weak_ptr;
它与shared_ptr互操作,意思是weak_ptr一般是配合shared_ptr使用的。
weak_ptr特点
weak_ptr的成员函数
constructors
/*
构造一个新的weak_ptr,它可能与r共享一个对象。
*/
//(1)无参构造
constexpr weak_ptr() noexcept;
//(2)拷贝构造
//它与r共享管理一个对象
weak_ptr( const weak_ptr& r ) noexcept;
template< class Y >
weak_ptr( const weak_ptr<Y>& r ) noexcept;
template< class Y >
weak_ptr( const std::experimental::shared_ptr<Y>& r ) noexcept;
//(3)移动拷贝构造
//将weak_ptr实例从r移动到*this。在此之后,r为空,r.use_count() = 0
weak_ptr( weak_ptr&& r ) noexcept;
template< class Y >
weak_ptr( weak_ptr<Y>&& r ) noexcept;
从上面看,weak_ptr并没有一个可以直接传需要托管对象的构造函数。
destructor
//销毁weak_ptr对象。对管理对象没有影响
~weak_ptr();
operator=
/*
将托管对象替换为r所管理的对象,该对象与r共享。如果r不管理任何对象,*this也不管理任何对象
等价于std::weak_ptr(r).swap(*this)
*/
//(1)~(3)等价于std::weak_ptr(r).swap(*this)
(1) (since C++11)
weak_ptr& operator=( const weak_ptr& r ) noexcept;
(2) (since C++11)
template< class Y >
weak_ptr& operator=( const weak_ptr<Y>& r ) noexcept;
(3) (since C++11)
template< class Y >
weak_ptr& operator=( const shared_ptr<Y>& r ) noexcept;
//(4)~(5)等价于std::weak_ptr(std::move(r)).swap(*this)
(4) (since C++14)
weak_ptr& operator=( weak_ptr&& r ) noexcept;
(5) (since C++14)
template< class Y >
weak_ptr& operator=( weak_ptr<Y>&& r ) noexcept;
reset
//释放对托管对象的引用。在调用*之后,它不管理任何对象。
void reset() noexcept;
swap
//交换*this和r的内容
//用来交换内容的智能指针r
void swap( weak_ptr& r ) noexcept;
use_count
//返回共享管理对象所有权的shared_ptr实例的数量,如果管理对象已经被删除,则返回0,即*this为空。
long use_count() const noexcept;
weak_ptr源码解析
constructors
constexpr __weak_ptr() noexcept
: _M_ptr(nullptr), _M_refcount()
{ }
template<typename _Yp, typename = _Compatible<_Yp>>
__weak_ptr(const __shared_ptr<_Yp, _Lp>& __r) noexcept
: _M_ptr(__r._M_ptr), _M_refcount(__r._M_refcount)
{ }
__weak_ptr(__weak_ptr&& __r) noexcept
: _M_ptr(__r._M_ptr), _M_refcount(std::move(__r._M_refcount))
{ __r._M_ptr = nullptr; }
operator=
template<typename _Yp>
_Assignable<_Yp> operator=(const __shared_ptr<_Yp, _Lp>& __r) noexcept {
_M_ptr = __r._M_ptr;
_M_refcount = __r._M_refcount;
return *this;
}
__weak_ptr& operator=(__weak_ptr&& __r) noexcept {
_M_ptr = __r._M_ptr;
_M_refcount = std::move(__r._M_refcount);
__r._M_ptr = nullptr;
return *this;
}
use_count
long use_count() const noexcept
{ return _M_refcount._M_get_use_count(); }
reset和swap
void reset() noexcept
{ __weak_ptr().swap(*this); }
swap(__weak_ptr& __s) noexcept {
std::swap(_M_ptr, __s._M_ptr);
_M_refcount._M_swap(__s._M_refcount);
}
weak_ptr的使用例子
void test7() {
Computer *c = new Computer("lenovo", 8000);
shared_ptr<Computer> sp(c);
weak_ptr<Computer> wp(sp);
cout << "sp.use_count() = " << sp.use_count() << endl;
cout << "wp.use_count() = " << wp.use_count() << endl;
wp.reset();
cout << endl;
cout << "sp.use_count() = " << sp.use_count() << endl;
cout << "wp.use_count() = " << wp.use_count() << endl;
}
Computer(const char *, double)
sp.use_count() = 1
wp.use_count() = 1
sp.use_count() = 1
wp.use_count() = 0
~Computer()
可以发现是weak_ptr进行对象的托管,并没有将引用计数加1
void test8() {
Computer *c = new Computer("lenovo", 8000);
Computer *c2 = new Computer("huawei", 6000);
shared_ptr<Computer> sp(c);
shared_ptr<Computer> sp2(c2);
weak_ptr<Computer> wp(sp);
cout << "sp.use_count() = " << sp.use_count() << endl;
cout << "wp.use_count() = " << wp.use_count() << endl;
sp = sp2;
cout << endl;
cout << "wp.use_count() = " << wp.use_count() << endl;
}
Computer(const char *, double)
Computer(const char *, double)
sp.use_count() = 1
wp.use_count() = 1
~Computer()
wp.use_count() = 0
~Computer()
可以发现,若将唯一托管c对象的shared_ptr转而去托管别的对象,此时不会在意这个对象是否被weak_ptr所管理,只会根据shared_ptr的个数来进行引用计数的改变。因此即使,没有对weak_ptr进行修改,weak_ptr此时管理的对象也被释放了。
强引用与弱引用
所谓的强引用,如shared_ptr在多个shared_ptr对同一个对象进行托管时,会进行引用计数加1的操作。
而弱引用,如weak_ptr,在进行托管对象时,并不会使引用计数加1。