Modern C++ std::any的实现原理

1. 前言

std::any 是 C++17 中引入的一个新特性,它是一个类型安全的容器,可以在其中存储任何类型(但此类型必须可拷贝构造)的值,包括基本类型、自定义类型、指针等。相比于void* 指针,std::any 更为类型安全,可以避免由于类型转换错误而导致的运行时错误。

std::any 获取值时需要指定正确的类型。如果尝试获取的类型与存储的类型不匹配,将抛出 std::bad_any_cast 异常。

2. std::any初体验

cppreference上就有一个例子,copy过来供大家学习:

#include <any>
#include <iostream>int main()
{std::cout << std::boolalpha;// any typestd::any a = 1;std::cout << a.type().name() << ": " << std::any_cast<int>(a) << '\n';a = 3.14;std::cout << a.type().name() << ": " << std::any_cast<double>(a) << '\n';a = true;std::cout << a.type().name() << ": " << std::any_cast<bool>(a) << '\n';// bad casttry{a = 1;std::cout << std::any_cast<float>(a) << '\n';}catch (const std::bad_any_cast& e){std::cout << e.what() << '\n';}// has valuea = 2;if (a.has_value())std::cout << a.type().name() << ": " << std::any_cast<int>(a) << '\n';// reseta.reset();if (!a.has_value())std::cout << "no value\n";// pointer to contained dataa = 3;int* i = std::any_cast<int>(&a);std::cout << *i << '\n';
}

输出:

int: 1
double: 3.14
bool: true
bad any_cast
int: 2
no value
3 

正如介绍中所说:

std::any可以有值,可以无值,还能存储int/double/bool等等类型数据,获取值时如果类型不对会抛出异常。

3. 原理-存储

any的存储是所有container中最简单的一个了,全部实现才600来行,通读一点问题没有。

any就是一个普通的类,没有任何模板参数,其 实现 在文件/usr/include/c++/11/any中。

class std::any {private:void (*_M_manager)(std::any::_Op, const std::any *, std::any::_Arg *);std::any::_Storage _M_storage;
}union std::any::_Storage {void *_M_ptr;std::aligned_storage<8, 8>::type _M_buffer; //size与指针相同,要求8字节对齐
}

std::any只有两项数据成员:

  1. _M_storage:数据存在这。因为用到了小对象优化,所以是一个union,笼统的讲:小对象用栈空间_M_buffer, 大点的对象要在堆上分配空间,由_M_ptr指向,不过这么讲不是很准确。
  2. _M_manager:一个特殊的函数,用来操作_M_storage,操作类型包括:访问、克隆(针对any copy)、移动(针对move语义)等等。

本节着重于第一条_M_storage,第二条我们在后面的“原理-初始化” 和 “原理-获取数据 ”中会详细叙述。 

3.1 存储-直观认识

关于_M_storage的内容,我们先看两个实例,从例子中有个直观认识:

std::any a = 1;

_M_ptr=1肯定不合法,1是按字节存入了char _M_buffer[8], 我的机器因为是小端故第一个字节是\001. 所以这里用了小对象优化。

再给a赋值为一个字符串,看看8个字节存不下的情况:

a = string("mzhai");

 可以看到new出了新空间,在堆上存储了string “mzhai”,由_M_ptr指向它。

3.2 存储-源码

OK,让我们看看源码,它是如何判断用不用小对象优化的哪?

 94     template<typename _Tp, typename _Safe = is_nothrow_move_constructible<_Tp>,95          bool _Fits = (sizeof(_Tp) <= sizeof(_Storage))96               && (alignof(_Tp) <= alignof(_Storage))>97       using _Internal = std::integral_constant<bool, _Safe::value && _Fits>;

_Internal见名知意,用“内部”栈空间的意思,其值由两点确定:

  1. _Tp必须是可移动构造 且 不抛异常的。
  2. _Tp的size必须小于预留的栈空间大小(8字节),且对齐要求小于等于8。

4. 原理-初始化

现在我们已经知道了any有两个重要的成员变量 以及 数据存在哪。我们想进一步看看如何由别的数据类型初始化一个any对象哪?这两个成员变量都是如何赋的值哪?

除了默认构造函数还有四种方式初始化一个any对象:

any(_Tp&& __value) //调用_Tp copy ctor/move ctor
any(in_place_type_t<_Tp>, _Args&&... __args)  //调用_Tp parameterized ctor
any(in_place_type_t<_Tp>, initializer_list<_Up> __il, _Args&&... __args) //调用_Tp parameterized ctoroperator=(_Tp&& __rhs)

如果你不太熟悉前三种用法,请参考官方链接。

一旦初始化了一个any对象,就可以用它的本身的copy ctor/move ctor来初始化别的any对象,所以上面这四个是基础。

前三个很相似,我是指它们的流程,流程分三步:

1. 它们都有激活的条件至少要求Tp是可拷贝构造的

比如第一个要求:is_copy_constructible<_VTp>

后面的要求更多,不仅仅要求is_copy_constructible,在此不再展开介绍。

2. 初始化_M_manager它是一个函数指针,要么指向小对象处理函数_Manager_internal::_S_manage,要么指向非小对象处理函数_Manager_external::_S_manage。

    template <typename _Tp, typename _VTp = _Decay_if_not_any<_Tp>,typename _Mgr = _Manager<_VTp>,typename = _Require<__not_<__is_in_place_type<_VTp>>,is_copy_constructible<_VTp>>>any(_Tp&& __value): _M_manager(&_Mgr::_S_manage)template<typename _Tp, typename _Safe = is_nothrow_move_constructible<_Tp>,bool _Fits = (sizeof(_Tp) <= sizeof(_Storage))&& (alignof(_Tp) <= alignof(_Storage))>using _Internal = std::integral_constant<bool, _Safe::value && _Fits>;template<typename _Tp>struct _Manager_internal; // uses small-object optimizationtemplate<typename _Tp>struct _Manager_external; // creates contained object on the heap//根据前面一节"原理-存储"中介绍的判断来决定用不用小对象优化template<typename _Tp>using _Manager = conditional_t<_Internal<_Tp>::value,_Manager_internal<_Tp>,_Manager_external<_Tp>>;

无论_Manager_internal还是_Manager_external 都定义了四个相同的静态函数:

  1.  _S_manage(_Op __which, const any* __anyp, _Arg* __arg); 
  2. _S_create(_Storage& __storage, _Up&& __value)       //下面的“存入数据”会用到
  3. _S_create(_Storage& __storage, _Args&&... __args)  //下面的“存入数据”会用到
  4. _S_access(const _Storage& __storage)                      //any_cast会用到

我们的关注点在_S_manage,简单比较下_Manager_internal::_S_manage 与 _Manager_external::_S_manage的不同:

template<typename _Tp>    any::_Manager_internal<_Tp>::_S_manage(_Op __which, const any* __any, _Arg* __arg){// 从char _M_storage._M_buffer[8]中取数据auto __ptr = reinterpret_cast<const _Tp*>(&__any->_M_storage._M_buffer);switch (__which){case _Op_access:__arg->_M_obj = const_cast<_Tp*>(__ptr);break;template<typename _Tp>voidany::_Manager_external<_Tp>::_S_manage(_Op __which, const any* __any, _Arg* __arg){// 从_M_storage._M_ptr指向的内存中取数据auto __ptr = static_cast<const _Tp*>(__any->_M_storage._M_ptr);switch (__which){case _Op_access:__arg->_M_obj = const_cast<_Tp*>(__ptr);break;

3. 存入数据

any中的两大数据成员之一_M_manager已经搞定,下一步就是_M_storage。

_M_storage的初始化,无论是三种初始化中的哪种,都是委托给_Mgr::_S_create完成的。比如第一种:

      any(_Tp&& __value): _M_manager(&_Mgr::_S_manage){_Mgr::_S_create(_M_storage, std::forward<_Tp>(__value));}

当然根据是否应用了SSO, _S_create也分两种情况:

1. _Manager_internal 调用了placement new在原有内存上构造Tp:

template<typename _Up>static void_S_create(_Storage& __storage, _Up&& __value){void* __addr = &__storage._M_buffer;::new (__addr) _Tp(std::forward<_Up>(__value)); //调用copy ctor/move ctor}template<typename... _Args>static void_S_create(_Storage& __storage, _Args&&... __args){void* __addr = &__storage._M_buffer;::new (__addr) _Tp(std::forward<_Args>(__args)...); //调用parameterized ctor}

 2. _Manager_external在堆上构造Tp

	template<typename _Up>static void_S_create(_Storage& __storage, _Up&& __value){__storage._M_ptr = new _Tp(std::forward<_Up>(__value));}template<typename... _Args>static void_S_create(_Storage& __storage, _Args&&... __args){__storage._M_ptr = new _Tp(std::forward<_Args>(__args)...);}

OK,前三种初始化讲完了,来看下第四种:assignement operator 

   template<typename _Tp>enable_if_t<is_copy_constructible<_Decay_if_not_any<_Tp>>::value, any&>operator=(_Tp&& __rhs){*this = any(std::forward<_Tp>(__rhs));return *this;}

它先调用了第一种初始化方式构造了一个临时any对象,再调用any的mtor把数据move过去,move委托给了_Manager_**ternal::_S_manage

    any(any&& __other) noexcept{if (!__other.has_value())_M_manager = nullptr;else{_Arg __arg;__arg._M_any = this;__other._M_manager(_Op_xfer, &__other, &__arg);}}

5. 原理-获取数据 

数据的获取必须通过any_cast,其语法如下:

无论是哪种形式,都会调用到__any_caster:

  template<typename _Tp>void* __any_caster(const any* __any){// any_cast<T> returns non-null if __any->type() == typeid(T) and// typeid(T) ignores cv-qualifiers so remove them:using _Up = remove_cv_t<_Tp>;// The contained value has a decayed type, so if decay_t<U> is not U,// then it's not possible to have a contained value of type U:if constexpr (!is_same_v<decay_t<_Up>, _Up>)return nullptr;// 又一次要求存储的类型必须拷贝可构造!else if constexpr (!is_copy_constructible_v<_Up>)return nullptr;// First try comparing function addresses, which works without RTTIelse if (__any->_M_manager == &any::_Manager<_Up>::_S_manage
#if __cpp_rtti|| __any->type() == typeid(_Tp)
#endif){return any::_Manager<_Up>::_S_access(__any->_M_storage); //真正的访问数据}return nullptr;}

 当然根据是否用了SSO,依然有两种情况。代码比较简单,我就不解释了。

//_Manager_internal	 SSOstatic _Tp*_S_access(const _Storage& __storage){// The contained object is in __storage._M_bufferconst void* __addr = &__storage._M_buffer;return static_cast<_Tp*>(const_cast<void*>(__addr));}//_Manager_externalstatic _Tp*_S_access(const _Storage& __storage){// The contained object is in *__storage._M_ptrreturn static_cast<_Tp*>(__storage._M_ptr);}

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

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

相关文章

NC65 rest接口 开发 NC65接口开发

一、在对应模块META-INF下编写 xxx.rest 文件,也要放在Home里对应的目录下。 二、开发接口&#xff0c;继承extends AbstractUAPRestResource&#xff0c;&#xff08;有的项目会继承别的方法如&#xff1a;AbstractNCCRestResource&#xff0c;MTFRestResource&#xff1b;有…

使用 kubeadm 部署k8s集群

一、所有节点系统初始化 1、常规初始化 2、内核版本升级以及内核限制文件参数修改 还可以考虑将旧版本的内核卸载 二、准备nginx负载均衡器和keepalived nginx四层代理&#xff1a; keepalived配置&#xff1a; nginx检测脚本&#xff1a; 三、所有节点部署docker&#xff0c…

SQL函数学习记录

聚合函数 函数是编程语言的基础之一&#xff0c;在对数字的运算中&#xff0c;我们用的最多的就是聚合函数&#xff0c;本篇接下来就详细阐述下SQL中聚合函数的运用。 什么是聚合函数&#xff08;aggregate function&#xff09;&#xff1f; 聚合函数指的是对一组值执行计算…

2023秋季飞书未来无限大会--随笔

这个时代的飞书 数字时代 工作协同平台 AI时代 帮助企业和个人用好AI 企业如何引用大模型能力&#xff1f; 智慧体— 接近人&#xff0c;有进步空间智能伙伴 用时代的科技打造爱不释手的好产品 移动互联网 – 改变信息分发方式 大模型 –自然的人机交互方式 业务协同 …

如何使用便签快速分类工作待办事项

在日常工作和生活中&#xff0c;我们经常需要处理各种各样的待办事项。而有效地分类这些任务&#xff0c;可以帮助我们更好地管理时间和提高工作效率。使用便签是一种简单而实用的方法&#xff0c;下面将介绍如何利用好用便签来快速分类工作待办事项。 首先&#xff0c;你可以…

【数据结构和算法初阶(C语言)】链表-单链表(手撕详讲单链表增删查改)

目录 1.前言&#xff1a;顺序表回顾&#xff1a; 1.1顺序表的优缺点 2.主角----链表 2.1链表的概念 2.2定义一个单链表的具体实现代码方式 3.单链表对数据的管理----增删查改 3.1单链表的创建 3.2单链表的遍历实现 3.2.1利用遍历实现一个打印我们链表内容的函数的函数…

【前端素材】推荐优质后台管理系统Salreo平台模板(附源码)

一、需求分析 当我们从多个层次来详细分析后台管理系统时&#xff0c;可以将其功能和定义进一步细分&#xff0c;以便更好地理解其在不同方面的作用和实际运作。 1. 结构层次 在结构层次上&#xff0c;后台管理系统可以分为以下几个部分&#xff1a; a. 辅助功能模块&#…

Mycat核心教程--ZooKeeper集群搭建【三】

Mycat核心教程--ZooKeeper集群搭建 八、 ZooKeeper集群搭建8.1.ZooKeeper简介8.2.数据复制的好处8.3.Zookeeper设计目的8.4.zookeeper集群包括3种角色8.4.1.Leader角色8.4.2.Follower 角色8.4.3.Observer 角色 8.5.zookeeper集群工作流程8.6.zookeeper集群节点数量为奇数&#…

JS进阶——深入对象

版权声明 本文章来源于B站上的某马课程&#xff0c;由本人整理&#xff0c;仅供学习交流使用。如涉及侵权问题&#xff0c;请立即与本人联系&#xff0c;本人将积极配合删除相关内容。感谢理解和支持&#xff0c;本人致力于维护原创作品的权益&#xff0c;共同营造一个尊重知识…

liunx操作系统 进程的基本概念

进程的基本概念 计算机结构体系冯诺依曼 操作系统的管理进程进程的特性标识符系统的调用 创建新的进程 进程的状态进程队列进程的状态在liunx查看进程状态、 计算机结构体系 冯诺依曼 在没有存储器之前&#xff0c;所有的信息都是直接进入CPU&#xff0c;这样效率很差&#xf…

智慧公厕:打造智慧城市环境卫生新标杆

随着科技的不断发展和城市化进程的加速推进&#xff0c;智慧城市建设已经成为各地政府和企业关注的焦点。而作为智慧城市环境卫生管理的基础设施&#xff0c;智慧公厕的建设和发展也备受重视&#xff0c;被誉为智慧城市的新标杆。本文以智慧公厕源头厂家广州中期科技有限公司&a…

Java代码实现获取本机服务的IP地址

要通过Java代码获取一个IP地址的信息&#xff0c;你可以使用多种方法。其中一种常见的做法是通过IP地址查询服务API来获取详细信息&#xff0c;比如地理位置、ISP&#xff08;互联网服务提供商&#xff09;等。这里有一个简单的例子&#xff0c;展示如何使用Java代码调用一个公…

RRT算法学习及MATLAB演示

文章目录 1 前言2 算法简介3 MATLAB实现3.1 定义地图3.2 绘制地图3.3 定义参数3.4 绘制起点和终点3.5 RRT算法3.5.1 代码3.5.2 效果3.5.3 代码解读 4 参考5 完整代码 1 前言 RRT&#xff08;Rapid Random Tree&#xff09;算法&#xff0c;即快速随机树算法&#xff0c;是LaVa…

Latex中大括号书写多行方式【已解决】

在写论文时需要写一个非1即0的公式&#xff0c;因此写了这篇文章。 本文主要分为两个部分&#xff1a;1.在括号外赋值、2、在括号内赋值 1.在括号外赋值 示例 \begin{equation}A\begin{cases}1x, x \textgreater 0\\1-x, x \leq 0 \end{cases} \label{KD} \end{equation} 效…

Dockerfile(4) - RUN 指令详解

RUN 运行命令 shell 形式 命令在 shell 中运行Linux 上默认为 /bin/sh -cWindows 上 cmd /S /C RUN <command> exec 形式 RUN ["executable", "param1", "param2"] 必须双引号&#xff0c;不能是单引号 两种写法的实际栗子 RUN …

CrossOver2024电脑虚拟机软件详细介绍概述

CrossOver是由CodeWeavers开发的一款系统兼容软件&#xff0c;它能够在Mac和Linux操作系统上直接运行Windows应用程序&#xff0c;而无需创建或启动完整的Windows虚拟机。CrossOver通过模拟Windows应用程序所需的运行环境&#xff0c;实现了跨平台的无缝集成和高效运行。 Cross…

unity学习(42)——创建(create)角色脚本(panel)——UserHandler(收)+CreateClick(发)——服务器收包1

1.首先保证服务器接受到的信息正确&#xff0c;在服务器的LogicHandler.cs中做第一次分拣&#xff1a; public void process(Session session, SocketModel model) {try{switch (model.Type){case 0:LoginHandler.getInstance().process(session, model);break;case 1:MapHand…

振动解调用的包络谱计算

1缘起 在振动分析中&#xff0c;对于一些高频频点的分析计算&#xff0c;使用包络谱技术&#xff0c;进而得到特化谱是最适宜的。 1.1 包络谱是什么样子的&#xff1f; 我们看matlab信号分析中提供的一个实例&#xff1a; https://www.mathworks.com/help/signal/ug/comput…

07_html

文章目录 引言前端概述分类 HTML快速入门重要的body标签注释hr标签br标签一些常见的标签标题标签div标签span标签p标签a标签img标签路径问题 ol和ul标签table标签input标签&#xff08;表单元素&#xff09;textarea标签&#xff08;表单元素&#xff09;select标签&#xff08…

软考50-上午题-【数据库】-SQL访问控制

一、SQL访问控制 数据控制&#xff0c;控制的是用户对数据的存储权力&#xff0c;由DBA决定。 DBA&#xff1a;数据库管理员。 DBMS数据控制应该具有一下功能&#xff1a; 1-1、授权语句格式 说明&#xff1a; 示例&#xff1a; 1-2、收回权限语句格式 示例&#xff1a; PUBLI…