连锁酒店网站方案,跨境电商卖什么产品最赚钱,wordpress侧栏导航栏,seo关键词优化公司官网文章目录 智能指针std::auto_ptr std::unique_ptrstd::shared_ptrstd::shared_ptr的线程安全问题std::weak_ptr 智能指针
std::auto_ptr
管理权转移
auto_ptr是C98中引入的智能指针#xff0c;auto_ptr通过管理权转移的方式解决智能指针的拷贝问题#xff0c;保证一个资源… 文章目录 智能指针std::auto_ptr std::unique_ptrstd::shared_ptrstd::shared_ptr的线程安全问题std::weak_ptr 智能指针
std::auto_ptr
管理权转移
auto_ptr是C98中引入的智能指针auto_ptr通过管理权转移的方式解决智能指针的拷贝问题保证一个资源在任何时刻都只有一个对象在对其进行管理这时同一个资源就不会被多次释放了4
class A
{
public:// 构造函数初始化列表中给成员变量_a赋值A(int a 0) : _a(a) {std::cout A(int a 0) std::endl;}// 析构函数~A() {std::cout ~A() std::endl;}
private:int _a;
};int main()
{auto_ptrA ap1(new A(1));auto_ptrA ap2(new A(2));//管理权转移拷贝时会把被拷贝对象的资源管理权转移给拷贝对象导致被拷贝对象悬空auto_ptrA ap3(ap1);return 0;
}int main()
{
//std::auto_ptrint ap1(new int(1));//管理权转移拷贝时会把被拷贝对象的资源管理权转移给拷贝对象导致被拷贝对象悬空std::auto_ptrint ap2(ap1);*ap2 10;//*ap1 20; //errorstd::auto_ptrint ap3(new int(1));std::auto_ptrint ap4(new int(2));ap3 ap4;return 0;
}
一个对象的管理权转移后也就意味着该对象不能再用对原来管理的资源进行访问了否则程序就会崩溃因此使用auto_ptr之前必须先了解它的机制否则程序很容易出问题很多公司也都明确规定了禁止使用auto_ptr
简易版的auto_ptr的实现
1、在构造函数中获取资源在析构函数中释放资源利用对象的生命周期来控制资源。 2、对*和-运算符进行重载使auto_ptr对象具有指针一样的行为。 3、在拷贝构造函数中用传入对象管理的资源来构造当前对象并将传入对象管理资源的指针置空。 4、在拷贝赋值函数中先将当前对象管理的资源释放然后再接管传入对象管理的资源最后将传入对象管理资源的指针置空
namespace cxq
{templateclass Tclass auto_ptr{public://RAIIauto_ptr(T* ptr nullptr):_ptr(ptr){}~auto_ptr(){if (_ptr ! nullptr){cout delete: _ptr endl;delete _ptr;_ptr nullptr;}}auto_ptr(auto_ptrT ap):_ptr(ap._ptr){ap._ptr nullptr; //管理权转移后ap被置空}auto_ptr operator(auto_ptrT ap){if (this ! ap){delete _ptr; //释放自己管理的资源_ptr ap._ptr; //接管ap对象的资源ap._ptr nullptr; //管理权转移后ap被置空}return *this;}//可以像指针一样使用T operator*(){return *_ptr;}T* operator-(){return _ptr;}private:T* _ptr; //管理的资源};
} std::unique_ptr
unique_ptr是C11中引入的智能指针unique_ptr通过防拷贝的方式解决智能指针的拷贝问题也就是简单粗暴的防止对智能指针对象进行拷贝这样也能保证资源不会被多次释放
int main()
{std::unique_ptrint up1(new int(0));//std::unique_ptrint up2(up1); //errorreturn 0;
}
简易版的unique_ptr的实现步骤如下
1、在构造函数中获取资源在析构函数中释放资源利用对象的生命周期来控制资源。 2、对*和-运算符进行重载使unique_ptr对象具有指针一样的行为。 3、用C98的方式将拷贝构造函数和拷贝赋值函数声明为私有或者用C11的方式在这两个函数后面加上delete防止外部调用
namespace cxq
{templateclass Tclass unique_ptr{public://RAIIunique_ptr(T* ptr nullptr):_ptr(ptr){}~unique_ptr(){if (_ptr ! nullptr){cout delete: _ptr endl;delete _ptr;_ptr nullptr;}}//可以像指针一样使用T operator*(){return *_ptr;}T* operator-(){return _ptr;}//防拷贝unique_ptr(unique_ptrT up) delete;unique_ptr operator(unique_ptrT up) delete;private:T* _ptr; //管理的资源};
}
std::shared_ptr
shared_ptr还会提供一个get函数用于获取其管理的资源
class Resource
{
public:void display() const {std::cout Resource is being used. std::endl;}
};void useResource(Resource* res)
{if (res){res-display();}
}int main()
{std::shared_ptrResource sp(new Resource());// 使用 get 函数获取原始指针Resource* rawPtr sp.get();// 使用原始指针调用函数useResource(rawPtr);// 直接使用 shared_ptr 调用成员函数sp-display();return 0;
}shared_ptr是C11中引入的智能指针shared_ptr通过引用计数的方式解决智能指针的拷贝问题。
每一个被管理的资源都有一个对应的引用计数通过这个引用计数记录着当前有多少个对象在管理着这块资源。当新增一个对象管理这块资源时则将该资源对应的引用计数进行当一个对象不再管理这块资源或该对象被析构时则将该资源对应的引用计数进行–。当一个资源的引用计数减为0时说明已经没有对象在管理这块资源了这时就可以将该资源进行释放了
通过这种引用计数的方式就能支持多个对象一起管理某一个资源也就是支持了智能指针的拷贝并且只有当一个资源对应的引用计数减为0时才会释放资源因此保证了同一个资源不会被释放多次
int main()
{// C11std::shared_ptrA sp1(new A(1));std::shared_ptrA sp2(new A(2));std::shared_ptrA sp3(sp1); // sp3 和 sp1 共享同一个A对象sp1-_a;sp3-_a; std::cout sp1-_a std::endl;return 0;
}namespace cxq
{templateclass Tclass shared_ptr{public://RAIIshared_ptr(T* ptr nullptr):_ptr(ptr), _pcount(new int(1)){}~shared_ptr(){if (--(*_pcount) 0){if (_ptr ! nullptr){cout delete: _ptr endl;delete _ptr;_ptr nullptr;}delete _pcount;_pcount nullptr;}}shared_ptr(shared_ptrT sp):_ptr(sp._ptr), _pcount(sp._pcount){(*_pcount);}shared_ptr operator(shared_ptrT sp){if (_ptr ! sp._ptr) //管理同一块空间的对象之间无需进行赋值操作{if (--(*_pcount) 0) //将管理的资源对应的引用计数--{cout delete: _ptr endl;delete _ptr;delete _pcount;}_ptr sp._ptr; //与sp对象一同管理它的资源_pcount sp._pcount; //获取sp对象管理的资源对应的引用计数(*_pcount); //新增一个对象来管理该资源引用计数}return *this;}//获取引用计数int use_count(){return *_pcount;}//可以像指针一样使用T operator*(){return *_ptr;}T* operator-(){return _ptr;}private:T* _ptr; //管理的资源int* _pcount; //管理的资源对应的引用计数 ,一个资源对应一个count 有几个资源就有几个count};
}
int main()
{cxq::shared_ptrint sp1(new int(1));cxq::shared_ptrint sp2(sp1);*sp1 10;*sp2 20;cout sp1.use_count() endl; //2cxq::shared_ptrint sp3(new int(1));cxq::shared_ptrint sp4(new int(2));sp3 sp4;cout sp3.use_count() endl; //2return 0;
}
如何理解引用计数需要存放在堆区
shared_ptr中的引用计数count不能单纯的定义成一个int类型的成员变量因为这就意味着每个shared_ptr对象都有一个自己的count成员变量而当多个对象要管理同一个资源时这几个对象应该用到的是同一个引用计数 shared_ptr中的引用计数count也不能定义成一个静态的成员变量因为静态成员变量是所有类型对象共享的这会导致管理相同资源的对象和管理不同资源的对象用到的都是同一个引用计数 如果将shared_ptr中的引用计数count定义成一个指针当一个资源第一次被管理时就在堆区开辟一块空间用于存储其对应的引用计数如果有其他对象也想要管理这个资源那么除了将这个资源给它之外还需要把这个引用计数也给它。
这时管理同一个资源的多个对象访问到的就是同一个引用计数而管理不同资源的对象访问到的就是不同的引用计数了相当于将各个资源与其对应的引用计数进行了绑定 std::shared_ptr的线程安全问题
模拟实现的shared_ptr还存在线程安全的问题由于管理同一个资源的多个对象的引用计数是共享的因此多个线程可能会同时对同一个引用计数进行自增或自减操作而自增和自减操作都不是原子操作因此需要通过加锁来对引用计数进行保护否则就会导致线程安全问题。
比如下面代码中用一个shared_ptr管理一个整型变量然后用两个线程分别对这个shared_ptr对象进行1000次拷贝操作这些对象被拷贝出来后又会立即被销毁
void func(cxq::shared_ptrint sp, size_t n)
{for (size_t i 0; i n; i){cxq::shared_ptrint copy(sp);}
}
int main()
{ std::shared_ptrint p(new int(0));const size_t n 1000;thread t1(func, p, n);thread t2(func, p, n);//线程等待t1.join();t2.join();cout p.use_count() endl; //预期1return 0;
}
在这个过程中两个线程会不断对引用计数进行自增和自减操作理论上最终两个线程执行完毕后引用计数的值应该是1因为拷贝出来的对象都被销毁了只剩下最初的shared_ptr对象还在管理这个整型变量但每次运行程序得到引用计数的值可能都是不一样的根本原因就是因为对引用计数的自增和自减不是原子操作
解决引用计数的线程安全问题本质就是要让对引用计数的自增和自减操作变成一个原子操作因此可以对引用计数的操作进行加锁保护
在shared_ptr类中新增互斥锁成员变量为了让管理同一个资源的多个线程访问到的是同一个互斥锁管理不同资源的线程访问到的是不同的互斥锁因此互斥锁也需要在堆区创建。在调用拷贝构造函数和拷贝赋值函数时除了需要将对应的资源和引用计数交给当前对象管理之外还需要将对应的互斥锁也交给当前对象。当一个资源对应的引用计数减为0时除了需要将对应的资源和引用计数进行释放由于互斥锁也是在堆区创建的因此还需要将对应的互斥锁进行释放。为了简化代码逻辑可以将拷贝构造函数和拷贝赋值函数中引用计数的自增操作提取出来封装成AddRef函数将拷贝赋值函数和析构函数中引用计数的自减操作提取出来封装成ReleaseRef函数这样就只需要对AddRef和ReleaseRef函数进行加锁保护即可
namespace cxq
{templateclass Tclass shared_ptr{private://引用计数void AddRef(){_pmutex-lock();(*_pcount);_pmutex-unlock();}//--引用计数void ReleaseRef(){_pmutex-lock();bool flag false;if (--(*_pcount) 0) //将管理的资源对应的引用计数--{if (_ptr ! nullptr){cout delete: _ptr endl;delete _ptr;_ptr nullptr;}delete _pcount;_pcount nullptr;flag true;}_pmutex-unlock();if (flag true){delete _pmutex;}}public://RAIIshared_ptr(T* ptr nullptr):_ptr(ptr), _pcount(new int(1)), _pmutex(new mutex){}~shared_ptr(){ReleaseRef();}shared_ptr(shared_ptrT sp):_ptr(sp._ptr), _pcount(sp._pcount), _pmutex(sp._pmutex){AddRef();}shared_ptr operator(shared_ptrT sp){if (_ptr ! sp._ptr) //管理同一块空间的对象之间无需进行赋值操作{ReleaseRef(); //将管理的资源对应的引用计数--_ptr sp._ptr; //与sp对象一同管理它的资源_pcount sp._pcount; //获取sp对象管理的资源对应的引用计数_pmutex sp._pmutex; //获取sp对象管理的资源对应的互斥锁AddRef(); //新增一个对象来管理该资源引用计数}return *this;}//获取引用计数int use_count(){return *_pcount;}//可以像指针一样使用T operator*(){return *_ptr;}T* operator-(){return _ptr;}private:T* _ptr; //管理的资源int* _pcount; //管理的资源对应的引用计数mutex* _pmutex; //管理的资源对应的互斥锁};
}
在ReleaseRef函数中当引用计数被减为0时需要释放互斥锁资源但不能在临界区中释放互斥锁因为后面还需要进行解锁操作因此代码中借助了一个flag变量通过flag变量来判断解锁后释放需要释放互斥锁资源。 shared_ptr只需要保证引用计数的线程安全问题而不需要保证管理的资源的线程安全问题就像原生指针管理一块内存空间一样原生指针只需要指向这块空间而这块空间的线程安全问题应该由这块空间的操作者来保证
std::weak_ptr
解决循环引用问题
weak_ptr是C11中引入的智能指针weak_ptr不是用来管理资源的释放的它主要是用来解决shared_ptr的循环引用问题的
weak_ptr支持用shared_ptr对象来构造weak_ptr对象构造出来的weak_ptr对象与shared_ptr对象管理同一个资源但不会增加这块资源对应的引用计数
weak_ptr的模拟实现
1、提供一个无参的构造函数比如刚才new ListNode时就会调用weak_ptr的无参的构造函数。 2、支持用shared_ptr对象拷贝构造weak_ptr对象构造时获取shared_ptr对象管理的资源。 3、支持用shared_ptr对象拷贝赋值给weak_ptr对象赋值时获取shared_ptr对象管理的资源。 4、对*和-运算符进行重载使weak_ptr对象具有指针一样的行为
namespace cxq
{templateclass Tclass weak_ptr{public:weak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptrT sp):_ptr(sp.get()){}weak_ptr operator(const shared_ptrT sp){_ptr sp.get();return *this;}//可以像指针一样使用T operator*(){return *_ptr;}T* operator-(){return _ptr;}private:T* _ptr; //管理的资源};
}