C++-智能指针
Posted The August
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++-智能指针相关的知识,希望对你有一定的参考价值。
智能指针
智能指针的使用及原理
- malloc出来的空间,没有进行释放,存在内存泄漏的问题。
- 异常安全问题。如果在malloc和free之间如果存在抛异常,那么还是有内存泄漏。这种问题就叫异常安全。
解决以上问题需要用智能指针
RAII
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
- 不需要显式地释放资源。
- 采用这种方式,对象所需的资源在其生命期内始终保持有效。
#include<iostream>
#include<exception>
using namespace std;
// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr
public:
SmartPtr(T* ptr = nullptr)
: _ptr(ptr)
~SmartPtr()
if (_ptr)
cout << _ptr << endl;
delete _ptr;
private:
T* _ptr;
;
void MergeSort()
int* tmp = new int(1);
SmartPtr<int> sp(tmp);
int main()
try
MergeSort();
catch (const exception& e)
cout << e.what() << endl;
return 0;
智能指针的原理
上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容,因此:AutoPtr模板类中还得需要将* 、->重载下,才可让其像指针一样去使用。
#include<iostream>
#include<exception>
using namespace std;
template<class T>
class SmartPtr
public:
SmartPtr(T* ptr = nullptr)
: _ptr(ptr)
~SmartPtr()
if (_ptr)
delete _ptr;
T& operator*()
return *_ptr;
T* operator->()
return _ptr;
private:
T* _ptr;
;
struct Date
Date()
:_year(0)
,_month(0)
,_day(0)
int _year;
int _month;
int _day;
;
int main()
SmartPtr<int> sp1(new int);
*sp1 = 10;
cout << *sp1 << endl;
SmartPtr<Date> sparray(new Date);
sparray->_year = 2018;
sparray->_month = 1;
sparray->_day = 1;
总结智能指针的原理:
- RAII特性
- 重载operator*和opertaor->,具有像指针一样的行为。
std::auto_ptr
auto_ptr的实现原理:
template<class T>
class auto_ptr
public:
// 1、RAII
// 2、重载operator* 和 operator-> 用起来像指针一样
auto_ptr(T* ptr)
:_ptr(ptr)
// sp2(sp1) 管理权转移
auto_ptr(auto_ptr<T>& sp)
:_ptr(sp._ptr)
sp._ptr = nullptr;
// ap2 = ap3;
auto_ptr<T>& operator=(auto_ptr<T>& ap)
if (this != &ap)
delete _ptr;
_ptr = ap._ptr;
ap._ptr = nullptr;
return *this;
~auto_ptr()
if (_ptr)
delete _ptr;
cout << _ptr << endl;
T& operator*()
return *_ptr;
T* operator->()
return _ptr;
private:
T* _ptr;
;
注:
- 这种方式是一种失败的设计,被质疑很久,一般公司都要求不要使用它。
- C++98版本的库中就提供了auto_ptr的智能指针
- C++库中的智能指针都定义在memory这个头文件中
- auto_ptr的问题:当对象拷贝或者赋值后,前面的对象就悬空了
- C++98中设计的auto_ptr问题是非常明显的,所以实际中很多公司明确规定了不能使用auto_ptr
std::unique_ptr
unique_ptr的实现原理
template<class T>
class unique_ptr
public:
// 1、RAII
// 2、重载operator* 和 operator-> 用起来像指针一样
unique_ptr(T* ptr)
:_ptr(ptr)
// 防拷贝
unique_ptr(const unique_ptr<T>& up) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;
~unique_ptr()
if (_ptr)
delete _ptr;
cout << _ptr << endl;
T& operator*()
return *_ptr;
T* operator->()
return _ptr;
private:
T* _ptr;
;
注意:
-
C++98 – 2011年中间这段,大家在使用boost库(第三方库)的智能指针
-
C++11 吸收了boost库中有价值的一些设计,将 boost – scoped_ptr shared_ptr weak_ptr这些智能指针转为C++11中的 – unique_ptr shared_ptr weak_ptr
-
设计思路,有些场景下面,智能指针仅仅用于管理资源,不需要拷贝。
std::shared_ptr
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
- shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
- 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
- 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
- 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
模拟实现一份简答的shared_ptr
template<class T>
struct DefaultDel
void operator()(T* ptr)
delete ptr;
;
template<class T,class D = DefaultDel<T>>
class shared_ptr
template<class T>
friend class weak_ptr;
public:
explicit shared_ptr(T* ptr=nullptr)
:_ptr(ptr)
,_pCount(new int)
,_pMtx(new mutex)
void add_ref()
_pMtx->lock();
(*_pCount)++;
_pMtx->unlock();
void release_ref()
bool flag = false;
_pMtx->lock();
if (--(*_pCount) == 0 && _ptr)
D del;
del (_ptr);
delete _pCount;
flag = true;
_ptr = nullptr;
_pMtx->unlock();
if (flag)
delete _pMtx;
shared_ptr(shared_ptr<T, D>& sp)
:_ptr(sp._ptr)
,_pCount(sp._pCount)
,_pMtx(sp._pMtx)
add_ref();
/*shared_ptr<T>& operator=(const shared_ptr<T>& sp)
if (_ptr!=sp._ptr)
release_ref();
_ptr = sp._ptr;
_pCount = sp._pCount;
_pMtx = sp._pMtx;
add_ref();
return *this;
*/
shared_ptr<T, D>& operator=(shared_ptr<T, D> sp)
swap(_ptr, sp._ptr);
swap(_pCount, sp._pCount);
swap(_pMtx, sp._pMtx);
return *this;
~shared_ptr()
release_ref();
T& operator*()
return *_ptr;
T* operator ->()
return _ptr;
T* get()
return _ptr;
int use_count()
return *_pCount;
private:
T* _ptr;
int* _pCount;
mutex* _pMtx;
;
- 设计思路:多个智能指针对象管理一块资源,这块资源,对应一个引用计数,析构时–计数,计数等于0时表示是最后一个管理对象,就释放资源
注意:
-
shared_ptr的线程安全问题分为两方面:
-
- 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或–,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2.这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以智能指针中引用计数++、–是需要加锁的,这样才能说引用计数的操作是线程安全的。
-
- 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。
补充:
- 模拟实现删除器的传递位置跟std的不太一样
- std的框架设计底层用一个类专门管理资源计数和释放,所以它可以再构造函数传参,把删除器类型传递给专门管理资源的这个类。
- 而模拟实现的这个是一体化的,只能lc::shared_ptr给删除器,析构函数才能拿到删除器
std::shared_ptr的循环引用、std::weak_ptr
weak_ptr的原理的模拟实现:
template<class T>
class weak_ptr
public:
weak_ptr()
:_ptr(nullptr)
weak_ptr(shared_ptr<T>& sp)
:_ptr(sp._ptr)
, _pCount(sp._pCount)
weak_ptr(weak_ptr<T>& sp)
:_ptr(sp._ptr)
, _pCount(sp._pCount)
weak_ptr<T>& operator=(shared_ptr<T>& sp)
_ptr = sp._ptr;
_pCount = sp._pCount;
return *this;
weak_ptr<T>& operator=(weak_ptr<T>& sp)
_ptr = sp._ptr;
_pCount = sp._pCount;
return *this;
private:
T* _ptr;
int* _pCount;
;
注意:
- weak_ptr不是常规意义的智能指针,没有接收一个原生指针的构造函数也不符合RAIl
- weak_ptr的对象,可以访问指向节点资源但是不参与节点资源释放管理,其实就是不增加计数
循环引用举例:
// 循环引用
struct ListNode
//问题:
//std::shared_ptr<ListNode> _prev;
//std::shared_ptr<ListNode> _next;
//解决方案:
std::weak_ptr<ListNode> _prev;
std::weak_ptr<ListNode> _next;
int val = 100;
~ListNode()
cout << "~ListNode()" << endl;
;
void test_cycle_ref()
std::shared_ptr<ListNode> node1(new ListNode);
std::shared_ptr<ListNode> node2(new ListNode);
//std::shared_ptr<ListNode> n3(new ListNode);
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node1->_next = node2;
node2->_prev = node1;
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
std::shared_ptr<ListNode> cur = node1;
while (cur.get() != nullptr)
cur->val++;
cout << cur->val << " ";
cur = cur->_next.lock();
// cur->_next.lock() weak_ptr的lock用指向的资源构造了一个shared_ptr
cout<<endl;
int main()
test_cycle_ref();
return 0;
循环引用分析:
- node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete。
- node1的_next指向node2,node2的_prev指向node1,引用计数变成2。
- node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。
- 也就是说_next析构了,node2就释放了。
- 也就是说_prev析构了,node1就释放了。
- 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放
补充:
如果不是new出来的对象,可以使用shared_ptr (shared_ptr设计了一个删除器来解决这个问题)
C++11和boost中智能指针的关系
- C++ 98 中产生了第一个智能指针auto_ptr.
- C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
- C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
- C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。
补充:
- 内存泄漏解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具。
- RAII思想除了可以用来设计智能指针,还可以用来设计守卫锁,防止异常安全导致的死锁问题。
#include <thread>
#include <mutex>
// C++11的库中也有一个lock_guard,
template<class Mutex>
class LockGuard
public:
LockGuard(Mutex& mtx)
:_mutex(mtx)
_mutex.lock();
~LockGuard()
_mutex.unlock();
LockGuard(const LockGuard<Mutex>&) = delete;
private:
// 注意这里必须使用引用,否则锁的就不是一个互斥量对象
Mutex& _mutex;
;
mutex mtx;
static int n = 0;
void Func()
for (size_t i = 0; i < 1000000; ++i)
LockGuard<mutex> lock(mtx);
++n;
int main()
int begin = clock();
thread t1(Func);
thread t2(Func);
t1.join();
t2.join();
int end = clock();
cout << n << endl;
cout << "cost time:" << end - begin << endl;
return 0;
以上是关于C++-智能指针的主要内容,如果未能解决你的问题,请参考以下文章