std::shared_ptr(基础、仿写、安全性)
创始人
2024-04-13 05:59:17

目录

一、c++参考手册

1、解释说明 

 2、代码示例

3、运行结果

二、对std::shared_ptr分析

1、shared_ptr基础

2、创建shared_ptr实例

3、访问所指对象

4、拷贝和赋值操作

5、检查引用计数

三、仿写std::shared_ptr代码

1、单一对象

2、数组对象

四、shared_ptr遇到问题

1、shared_ptr在多线程中遇到的问题

(1)、shared_ptr 的数据结构

 (2)、有三个shared_ptr对象

 3、其他问题:

4、相互引用(weak_ptr)


一、c++参考手册

1、解释说明 

 2、代码示例

#include 
#include 
#include 
#include 
#include struct Base
{Base() { std::cout << "  Base::Base()\n"; }// 注意:此处非虚析构函数 OK~Base() { std::cout << "  Base::~Base()\n"; }
};struct Derived: public Base
{Derived() { std::cout << "  Derived::Derived()\n"; }~Derived() { std::cout << "  Derived::~Derived()\n"; }
};void thr(std::shared_ptr p)
{std::this_thread::sleep_for(std::chrono::seconds(1));std::shared_ptr lp = p; // 线程安全,虽然自增共享的 use_count{static std::mutex io_mutex;std::lock_guard lk(io_mutex);std::cout << "local pointer in a thread:\n"<< "  lp.get() = " << lp.get()<< ", lp.use_count() = " << lp.use_count() << '\n';}
}int main()
{std::shared_ptr p = std::make_shared();std::cout << "Created a shared Derived (as a pointer to Base)\n"<< "  p.get() = " << p.get()<< ", p.use_count() = " << p.use_count() << '\n';std::thread t1(thr, p), t2(thr, p), t3(thr, p);p.reset(); // 从 main 释放所有权std::cout << "Shared ownership between 3 threads and released\n"<< "ownership from main:\n"<< "  p.get() = " << p.get()<< ", p.use_count() = " << p.use_count() << '\n';t1.join(); t2.join(); t3.join();std::cout << "All threads completed, the last one deleted Derived\n";
}

3、运行结果

二、对std::shared_ptr分析

1、shared_ptr基础

shared_ptr是一个引用计数智能指针,用于共享对象的所有有权,也就是说可以多个指针指向一个对象

class Object {
private:int value;
public:Object(int x = 0) :value(x) { cout << "Constructor Object ..." << endl; }~Object() { cout << "Destroy Object ..." << endl; }int& Value() { return value; }const int& Value()const { return value; }
};int main() {shared_ptr pObj(new Object(100));cout << (*pObj).Value() << endl;cout << "pObj 引用计数:" << pObj.use_count() << endl;shared_ptrpObj2 = pObj;cout << "pObj 引用计数:" << pObj.use_count() << endl;cout << "pObj 引用计数:" << pObj2.use_count() << endl;return 0;
} 

从上面这段代码中,我们对shared_ptr指针有了一些直观的了解。
一方面,跟STL中大多数容器类型一样, shared_ptr 也是模板类,因此在创建shared. ptr时需要指定其指向的类型。另一方面,正如其名一样,shared_ptr 指针允许让多个该类型的指针共享同一堆分配对象。同时shared_ptr 使用经典的“引用计数"方法来管理对象资源,每个shared_ptr对象关联一个共享的引用计数。
对于shared_ ptr 在拷贝和赋值时的行为是,每个shared _ptr 都有一个关联的计数值, 通常称为引用计数。无论何时我们拷贝一个shared_ptr,计数器都会递增。
例如,当用一个shared_ptr 初始化另一个 shred_ptr,或将它当做参数传递给一个函数以及作为函数的返回值时,它所关联的计数器就会递增。
当我们给shared_ ptr 赋予一个新值或是shared ptr 被销毁(例如一个局部的shared_ ptr离开其作用域)时,计数器就会递减。shared_ ptr 对象的计数器变为0,它就会自动释放自己所管理的对象。
对比我们上面的代码可以看到:当我们将一个指向Object对象的指针交给pObj管理后,其关联的引用计数为1。接下来,我们用pObj初始化pObj2,两者关联的引用计数值增加为2。随后,函数结束, pObj 和PObj2相继离开函数作用域,相应的引用计数值分别自减1最后变为0,于是Object对象被自动释放(调用其析构函数)。

2、创建shared_ptr实例

最安全和高效的方法是调用make_shared库函数,该函数会在堆中分配一个对象并初始化, 最后返回指向此对象的share_ptr实例。如果你不想使用make_shared,也可以先明确new出一个对象, 然后把其原始指针传递给share_ptr 的构造函数。

int main() {shared_ptr ptr = make_shared(10,'s');cout << *ptr << endl;int* p = new int(10);shared_ptr pInt(p);cout << *pInt << endl;
}

3、访问所指对象

shared_ptr的使用方式与普通指针的使用方式类似,既可以使用解引用操作符*获得原始对象进而访问其各个成员,也可以使用指针访问符->来访问原始对象的各个成员。

4、拷贝和赋值操作

我们可以用一个shared_ptr对象来初始化另一个share_ptr 实例,该操作会增加其引用计数值。

int main() {shared_ptr pStr = make_shared(10, 's');cout << pStr.use_count() << endl;shared_ptr pStr2(pStr);cout << pStr.use_count() << endl;cout << pStr2.use_count() << endl;
}

 如果sharedp_tr实例p和另一个shared_ptr 实例q所指向的类型相同或者可以相互转换,我们还可以进行诸如p =q这样赋值操作。该操作会递减p的引用计数值,递增q的引用计数值。

class Object {
private:string value;
public:Object(string x = "") :value(x) { cout << value << "Create Object.." << endl; }~Object() { cout << value << "Destroy Object.." << endl; }string& Value() { return value; }const string& Value() const { return value; }};
int main() {shared_ptr pObj = make_shared("a.tex");;shared_ptr pObj1 = make_shared("b.text");cout << pObj.use_count() << endl;cout << pObj1.use_count() << endl;pObj = pObj1;cout << pObj.use_count()<< endl;cout << pObj1.use_count() << endl;return 0;
} 

5、检查引用计数

shared_ptr提供两个检查共享引用计数值,分别是unique()和use_cout();

  • use_cout()效率比较低,适合测试和调试;
  • unique()返回的是true和false,检测是否原始指针的唯一拥有者。

三、仿写std::shared_ptr代码

1、单一对象

#include
template 
class MyDeletor
{
public:MyDeletor() = default;void operator ()(_Ty* ptr)const{if (ptr != nullptr){delete ptr;}}
};
template 
class RefCnt
{
public:_Ty* mptr;std::atomic_int ref;
public:RefCnt(_Ty* p = nullptr) :mptr(p), ref(mptr != nullptr) {}~RefCnt() {}
};
template>
class my_shared_ptr
{
private:RefCnt<_Ty>* ptr;_Dx mDeletor;
public:my_shared_ptr(_Ty* p = nullptr) :ptr(nullptr){if (p != nullptr){ptr = new RefCnt(p);}}my_shared_ptr(const my_shared_ptr& _Y) :ptr(_Y.ptr){if (ptr != nullptr){ptr->ref += 1;}}my_shared_ptr(my_shared_ptr&& _Y) :ptr(_Y.ptr){_Y.ptr = nullptr;}operator bool()const { return ptr != nullptr; }my_shared_ptr& operator=(const my_shared_ptr& _Y){if (this == &_Y || this->ptr == _Y.ptr)return this;if (ptr != nullptr && --ptr->ref == 0){ mDeletor(ptr);}ptr = _Y.ptr;if (ptr != nullptr){ptr->ref += 1;}return *this;}my_shared_ptr& operator=(my_shared_ptr&& _Y){if (this == &_Y)return this;if (this->ptr == _Y.ptr && this->ptr != nullptr && _Y.ptr != nullptr){this->ptr->ref -= 1;_Y.ptr = nullptr;return *this;}if (this->ptr != nullptr && --ptr->ref == 0){mDeletor(ptr);}ptr = _Y.ptr;_Y.ptr = nullptr;return *this;}void reset(_Ty* p = nullptr){if (this->ptr != nullptr && --this->ptr->ref == 0){mDeletor(ptr);	}ptr = new RefCnt<_Ty>(p);}~my_shared_ptr(){if (this->ptr != nullptr && --this->ptr->ref == 0){mDeletor(ptr->mptr);delete ptr;}ptr = nullptr;}_Ty* get()const{return ptr->mptr;}_Ty& operator*()const{return *get();	}_Ty* operator ->()const{return get();}size_t use_count()const{if (this->ptr == nullptr)return 0;return this->ptr->ref;}void swap(my_shared_ptr* r){std::swap(this->ptr, r->ptr);}
};

2、数组对象

template 
class MyDeletor<_Ty[]>
{
public:MyDeletor() = default;void operator ()(_Ty* ptr)const{if (ptr != nullptr){delete []ptr;}}
};
template 
class RefCnt
{
public:_Ty* mptr;std::atomic_int ref;
public:RefCnt(_Ty* p = nullptr) :mptr(p), ref(mptr != nullptr) {}~RefCnt() {}
};
template
class my_shared_ptr<_Ty[],_Dx>
{
private:RefCnt<_Ty>* ptr;_Dx mDeletor;
public:my_shared_ptr(_Ty* p = nullptr) :ptr(nullptr){if (p != nullptr){ptr = new RefCnt(p);}}my_shared_ptr(const my_shared_ptr& _Y) :ptr(_Y.ptr){if (ptr != nullptr){ptr->ref += 1;}}my_shared_ptr(my_shared_ptr&& _Y) :ptr(_Y.ptr){_Y.ptr = nullptr;}operator bool()const { return ptr != nullptr; }my_shared_ptr& operator=(const my_shared_ptr& _Y){if (this == &_Y || this->ptr == _Y.ptr)return this;if (ptr != nullptr && --ptr->ref == 0){mDeletor(ptr);}ptr = _Y.ptr;if (ptr != nullptr){ptr->ref += 1;}return *this;}my_shared_ptr& operator=(my_shared_ptr&& _Y){if (this == &_Y)return this;if (this->ptr == _Y.ptr && this->ptr != nullptr && _Y.ptr != nullptr){this->ptr->ref -= 1;_Y.ptr = nullptr;return *this;}if (this->ptr != nullptr && --ptr->ref == 0){mDeletor(ptr);}ptr = _Y.ptr;_Y.ptr = nullptr;return *this;}void reset(_Ty* p = nullptr){if (this->ptr != nullptr && --this->ptr->ref == 0){mDeletor(ptr);}ptr = new RefCnt<_Ty>(p);}~my_shared_ptr(){if (this->ptr != nullptr && --this->ptr->ref == 0){mDeletor(ptr->mptr);delete ptr;}ptr = nullptr;}_Ty* get()const{return ptr->mptr;}_Ty& operator*()const{return *get();}_Ty* operator ->()const{return get();}size_t use_count()const{if (this->ptr == nullptr)return 0;return this->ptr->ref;}void swap(my_shared_ptr* r){std::swap(this->ptr, r->ptr);}_Ty& operator[](const int idx)const{return ptr->mptr[idx];}
};

四、shared_ptr遇到问题

1、shared_ptr在多线程中遇到的问题

  • (1) (shared_ptr)的引用计数本身是线程安全(引用计数是原子操作)。
  • (2) 多个线程同时读同一个shared_ptr对象是线程安全的。
  • (3)如果是多个线程对同一个shared_ptr对象进行读和写,则需要加锁。
  • (4)多线程读写shared_ptr 所指向的同一个对象,不管是相同的shared_ptr对象,还是不同的shared_ptr对象,也需要加锁保护。

具体分析一下为什么“因为shared_ptr有两个数据成员,读写操作不能原子化”使得多线程读写同一个shared_ptr 对象需要加锁。

(1)、shared_ptr 的数据结构

shared_ptr 是引用计数型(reference counting)智能指针,几乎所有的实现都采用在堆(heap)上放个计数值(count)。

class Object {
private:int value;
public:Object(int x=0):value(x){}~Object() {}int& Value() { return value; }const int& Value() const { return value; }
};
int main() {shared_ptr apa(new Object(10));shared_ptr apb = apa;return  0;
} 

 (2)、有三个shared_ptr对象

 3、其他问题:

shared_ptr 作为unordered_map的key如果把sharedp_tr放到unordered_set中,或者用于unordered_map 的key,那么要小心hash table 退化为链表。
但是其hash_value是shared_ptr隐式转换为bool的结果。也就是说,如果不自定义hash函数,那么
unordered_ {set/map} 会退化为链表。
为什么要尽量使用make_shared?(申请被管理对象以及引用计数的内存;调用适当的构造函数初始化对象;返回一个shared_ptr为了节省一次内存分配,原来shared_ptr x(new Object (10) );需要为Object对象和RefCnt各分配一次内存,现在用make_ shared() 的话,可以一次分配一 块足够大的内存,供Object 和RefCnt对象容身。不过Object的构造函数所需的参数要传给make _shared后者再传给Object:: Object(),这只有在C++11里通过perfect forwarding(完美转发)才能完美解决。

4、相互引用(weak_ptr)

下一篇

相关内容

热门资讯

最新或2023(历届)六一儿童...  各位老师,同学:  大家好!  首先,祝所有同学六一儿童节快乐! 我想,我们不得不感慨,时间的飞逝...
中学生弘扬五四精神发言稿范文5...   【篇1】弘扬五四精神发言稿范文  亲爱的同学们:  大家好!    五月的春风情深意暖,五月的花...
qq签名档闪图女生 qq签名档... 1、小时候认为流血了就是很严重的事,不管疼不疼,先哭再说。 2、用文字寄托那一生的悲伤 、 ...
女生签名超拽 女生签名超拽 最... 1、现在沵是涐旳男人,沵不要再招惹别的女人。 2、人生最大的贵人,永远是——自己。取悦男人不如...
最新或2023(历届)霸道女生... 1、女人,喝醉的时候想的第一个人,便是伤她最深的人。 2、如烟旳女子,期待旳是如梦般浪漫旳依偎...