std::shared_ptr(基础、仿写、安全性)

news2025/7/14 10:44:59

目录

一、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 <iostream>
#include <memory>
#include <thread>
#include <chrono>
#include <mutex>
 
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<Base> p)
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::shared_ptr<Base> lp = p; // 线程安全,虽然自增共享的 use_count
    {
        static std::mutex io_mutex;
        std::lock_guard<std::mutex> 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<Base> p = std::make_shared<Derived>();
 
    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<Object> pObj(new Object(100));
    cout << (*pObj).Value() << endl;
    cout << "pObj 引用计数:" << pObj.use_count() << endl;
    shared_ptr<Object>pObj2 = 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<string> ptr = make_shared<string>(10,'s');
    cout << *ptr << endl;
    int* p = new int(10);
    shared_ptr<int> pInt(p);
    cout << *pInt << endl;
}

3、访问所指对象

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

4、拷贝和赋值操作

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

int main() {
    shared_ptr<string> pStr = make_shared<string>(10, 's');
    cout << pStr.use_count() << endl;
    shared_ptr<string> 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<Object> pObj = make_shared<Object>("a.tex");;
    shared_ptr<Object> pObj1 = make_shared<Object>("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<atomic>
template <class _Ty>
class MyDeletor
{
public:
	MyDeletor() = default;
	void operator ()(_Ty* ptr)const
	{
		if (ptr != nullptr)
		{
			delete ptr;
		}
	}
};
template <class _Ty>
class RefCnt
{
public:
	_Ty* mptr;
	std::atomic_int ref;
public:
	RefCnt(_Ty* p = nullptr) :mptr(p), ref(mptr != nullptr) {}
	~RefCnt() {}
};
template<class _Ty,class _Dx=MyDeletor<_Ty>>
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 _Ty>
class MyDeletor<_Ty[]>
{
public:
	MyDeletor() = default;
	void operator ()(_Ty* ptr)const
	{
		if (ptr != nullptr)
		{
			delete []ptr;
		}
	}
};
template <class _Ty>
class RefCnt
{
public:
	_Ty* mptr;
	std::atomic_int ref;
public:
	RefCnt(_Ty* p = nullptr) :mptr(p), ref(mptr != nullptr) {}
	~RefCnt() {}
};
template<class _Ty, class _Dx >
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<Object> apa(new Object(10));
    shared_ptr<Object> apb = apa;
    return  0;
}

 (2)、有三个shared_ptr<Object>对象

 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<Object> x(new Object (10) );需要为Object对象和RefCnt各分配一次内存,现在用make_ shared() 的话,可以一次分配一 块足够大的内存,供Object 和RefCnt对象容身。不过Object的构造函数所需的参数要传给make _shared后者再传给Object:: Object(),这只有在C++11里通过perfect forwarding(完美转发)才能完美解决。

4、相互引用(weak_ptr)

下一篇

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/38223.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MyBatis 环境搭建

MyBatis 环境搭建步骤 1.创建一张表和表对应的实体类 2.创建一个 maven 项目&#xff0c;把项目添加到 git 仓库 创建maven项目 教程见&#xff1a;Maven[项目构建工具]_chen☆的博客-CSDN博客 添加到git仓库&#xff1a; 3.在文件 pom.xml 添加 mybiatis 相关依赖(导入 MyBa…

Java - 利用Nacos做一个动态开关配置功能

Java - 利用Nacos做一个动态开关配置功能前言一. Nacos配置类编写二. 测试三. 展望前言 我公司里有一个Config配置功能&#xff08;我相信这是很普遍的一个功能&#xff09;。简单来说就是&#xff1a; 将相关的键值对放到这个Config配置系统里面。代码里通过这个Config配置系…

博客项目(前台功能实现)

博客项目(前台接口实现) 文章目录博客项目(前台接口实现)1.前置知识1.1Controller1.1.1ResponseResult类1.1.2该类的方法1.2Service1.3ServiceImpl1.4Mapper1.5Vo的理解1.6可能会用到的相关插件1.7设置字面量1.8后端接口测试工具2.热门文章接口分析2.1热门文章接口位置2.2接口的…

Internet Download Manager2023最新版下载器功能介绍

说到下载器在国内就不得不提迅雷&#xff0c;迅雷真是伟大&#xff0c;几乎垄断了国内的下载市场&#xff0c;的确&#xff0c;有的时候用迅雷可以下载到很不错的资源&#xff0c;但在没有VIP的情况下&#xff0c;迅雷是不友好的&#xff0c;相信使用迅雷的各位都有被限速过的经…

三、Eureka

文章目录一、认识服务提供者和服务调用者二、Eureka 的工作流程三、服务调用出现的问题及解决方法四、搭建 eureka-server五、注册 user-service、order-service六、在 order-service 完成服务拉取&#xff08;order 模块能访问 user 模块&#xff09;七、配置远程服务调用八、…

分布式锁:不同实现方式实践测评

Hello读者朋友们&#xff0c;今天打算分享一篇测评实践类的文章&#xff0c;用优雅的代码与真实的数据来讲述在分布式场景下&#xff0c;不同方式实现的分布式锁&#xff0c;分别探究每一种方式的性能情况与最终的优劣分析。 开门见山&#xff0c;我们先看一张表格&#xff0c…

fiddler安卓模拟器与ios手机抓包

一.安卓模拟器(雷电模拟器)抓包 1.1fiddler基本配置 1.2导出Fiddler证书 Tools -> Options -> HTTPS -> Actions -> Export Root Certificate to Desktop 在桌面上看到导出的FiddlerRoot.cer证书文件 1.3下载和安装openssl openssl下载地址 git终端输入 open…

蜂鸟E203学习(一)--RISC的前世今生

第一章 CPU之前世今生 1.1、CPU众生相 1.1.1 处理器&#xff08;cpu&#xff09;和处理器内核&#xff08;core&#xff09;的区分 处理器严格意义上是soc&#xff0c;包含了内核和其他设备或者存储器. 1.1.2 不同CPU架构的诞生时间 CPU架构诞生时间Intel 80861978年ARM19…

Opencv之Mat常用类成员(一篇就够了)

1. 重要类成员 data&#xff1a;数据存储的起始地址 (uchar*类型)&#xff1b;dims&#xff1a;矩阵维度。如 3 * 4 的矩阵为 2 维&#xff0c; 3 * 4 * 5 的为3维&#xff1b;channels()&#xff1a;通道数量&#xff0c;矩阵中表示一个元素所需要的值的个数。例&#xff1a;…

Docker-系统环境

Docker1.Docker与虚拟机的区别2.Docker主要解决的问题3.镜像和容器4.Docker的安装9.查找镜像10.常用命令11.安装数据库12.安装tomcat13.容器使用注意事项1.Docker与虚拟机的区别 Docker是开发运行和部署应用程序的开发管理平台&#xff0c;它类似于虚拟机&#xff0c;可以独立…

七、Feign

文章目录一、Feign实现远程调用1.替换RestTemplate发起远程调用&#xff0c;RestTemplate存在的问题&#xff1a;2.实现Feign远程调用&#xff1a;二、Feign的自定义日志1.Feign可修改的配置如下2.方式一&#xff1a;配置文件方式3.方式二&#xff1a;Java代码方式一、Feign实现…

Qt:信号与槽机制

说实话&#xff0c;Qt给我的感觉像一种魔改版c&#xff0c;不纯粹&#xff0c;看不到内部的源代码&#xff0c;也不知道一些宏是怎么实现的... 信号与槽内部机制 回归正题&#xff0c;其实学过设计模式的应该都能看出来&#xff0c;qt的这个机制是一个观察者模式&#xff1b; …

又解锁了一种OpenFeign的使用方式!

引言 Hello 大家好&#xff0c;这里是Anyin。 在关于OpenFeign那点事儿 - 使用篇 中和大家分享了关于OpenFeign在某些场景下的一些处理和使用方法&#xff0c;而今天Anyin再次解锁了OpenFeign的又一个使用场景&#xff0c;只能说真香。 在我们日常开发中&#xff0c;相信大家…

SSM框架-MyBatis基础

1. MyBatis简介 1.1 MyBatis历史 MyBatis最初是Apache的一个开源项目iBatis&#xff0c;2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下&#xff0c;iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github。 iBa…

Pipelines in Shell

本篇文章内容需要读者知道 shell 的一些语法和作用&#xff0c;知道 shell 的用途&#xff0c;和一些基本的用法。 这里可以查看原文&#xff1a;Pipelines in Shell 学习 shell 脚本必须要理解 pipeline 的概念&#xff0c;知道 command 的输入&#xff08;input&#xff09;和…

编译概念总结

一个很笨很笨的人的编译自救笔记。 1 程序设计语言 程序设计语言用于书写计算机程序的语言。语言的基础是一组记号和一组规则。根据规则由记号构成的记号串的总体就是语言。在程序设计语言中&#xff0c;这些记号串就是程序。 程序设计语言由三个方面的因素&#xff0c;语法…

[附源码]SSM计算机毕业设计商场日常维修管理系统JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

深度学习(19):nerf论文公式理解

注&#xff1a;有问题欢迎评论留言&#xff0c;但尽量不要喷呀。 1. nerf论文第四章翻译如下&#xff1a; 我们的5D神经辐射场将场景表示为空间任意点的体积密度和定向发射辐射&#xff08;directional emitted radiance&#xff09;。我们使用经典体积渲染&#xff08;class…

嗯哦哎辟 2022 游寄

虽然上次不是假的&#xff0c;但这次是真的寄了。 Day 0 虽然是南京本地人&#xff0c;但因疫情原因&#xff0c;晚上决定去住了酒店。 看了一眼考场&#xff0c;感觉位置小得离谱。不愧是 NOI 2022 团体总分第十的“强省”江苏。 刚开始去了 409&#xff0c;发现房间里一股…

C++ 基础入门

1、变量 作用&#xff1a;给一段指定的内存空间起名&#xff0c;方便操作这段内存。 2、常量 作用&#xff1a;用于记录程序中不可更改的数据 C中定义常量的两种方式&#xff1a; #define 宏常量&#xff1a; #define 常量名 常量值const修饰的变量&#xff1a;const数据类型 …