unique_ptr自定义删除器,_Compressed_pair利用偏特化减少存储的一些设计思路

主要是利用偏特化,

如果自定义删除器是空类(没有成员变量,可以有成员函数):

_Compressed_pair会继承删除器(删除器作为基类),但_Compressed_pair里不保存删除器对象,只保存指针,所以此时,可以把unique_ptr认为是裸指针;

如果不是空类:那么会同时保存删除器对象和指针。

避免错误的使用自定义unique_ptr deleter带来不必要的开销

避免错误的使用自定义unique_ptr deleter带来不必要的开销

LeeCarry

这世界太繁杂了,我只想守护自己的纯粹

25 人赞同了该文章

无意间看到

FOCUS:现代 C++:一文读懂智能指针595 赞同 · 12 评论文章

昂,一个unique_ptr要用40字节???第一反应是作者是不是笔误多打了个0?然而细看一下不对啊,这里应该是用64位测的,一个raw pointer是8字节,那应该不是笔误,再认真细看一下就明白了。

还是从头梳理一下吧,首先要知道unique_ptr和shared_ptr的自定义deleter方式并不一样,unique_ptr为了优化开销需要提供deleter的类型,这里仅讨论 unique_ptr。

struct FileCloserStruct {void operator()(FILE* fp) const {if (fp != nullptr) {fclose(fp);}}
};void FileCloserFunc(FILE* fp) {if (fp != nullptr) {fclose(fp);}
}auto FileCloserLambda = [](FILE* fp) {if (fp != nullptr) {fclose(fp);}
};int main() {std::unique_ptr<FILE, FileCloserStruct> uptr1(fopen("test_file.txt", "w"));std::cout << sizeof(uptr1) << std::endl;// ???std::unique_ptr<FILE, void(*)(FILE*)> uptr2(fopen("test_file1.txt", "w"), FileCloserFunc);std::cout << sizeof(uptr2) << std::endl;// ???std::unique_ptr<FILE, std::function<void(FILE*)>> uptr3(fopen("test_file2.txt", "w"), FileCloserLambda);std::cout << sizeof(uptr3) << std::endl;// ???std::unique_ptr<FILE, decltype(FileCloserLambda)> uptr4(fopen("test_file3.txt", "w"), FileCloserLambda);std::cout << sizeof(uptr4) << std::endl;// ???return 0;
}

假设都是在MSVC 32位程序上测,

先来看第一个:

	std::unique_ptr<FILE, FileCloserStruct> uptr1(fopen("test_file.txt", "w"));std::cout << sizeof(uptr1) << std::endl;// 4

如果这里只使用了无状态的自定义deleter其实和raw pointer是一样大小。

稍微深入一点点这里是如何优化掉deleter大小的,以MSVC源码为例,其他思想上应该都是一样的

这个是unique_ptr内部使用_Compressed_pair来存deleter和pointer。

根据_Compressed_pair的实现可以看出来利用模板偏特化进行了判断,如果deleter是个空基类并且可以继承的话,就不需要保存这个deleter类型的成员,直接继承这个deleter类型用EBO(空基类优化)[1]从对象布局中优化掉。

第二个:

	std::unique_ptr<FILE, void(*)(FILE*)> uptr2(fopen("test_file1.txt", "w"), FileCloserFunc);std::cout << sizeof(uptr2) << std::endl;// 8

这里传入的是函数指针,阻止了EBO,需要额外的变量来保存,所以就是8了。

第三个:

	std::unique_ptr<FILE, std::function<void(FILE*)>> uptr3(fopen("test_file2.txt", "w"), FileCloserLambda);std::cout << sizeof(uptr3) << std::endl;// 48

这也是一开始抛出的问题,虽然具体数字和最上面博主测出来的不一致,但这也跟环境有关,此处不深究,重点是,它太大了!

因为std::function本来就不是lambda的原类型,std::function是通用多态函数封装器,非常强大,但是这种强大也是有代价的,直接使用sizeof(std::function<xxx>)看它也可以发现它是需要一定的内存开销的[2]。而把std::function作为类型传给了unqiue_ptr deleter时,等于在unique_ptr里把这个std::function也给存了起来,开销自然就大了...

第四个:

	std::unique_ptr<FILE, decltype(FileCloserLambda)> uptr4(fopen("test_file3.txt", "w"), FileCloserLambda);std::cout << sizeof(uptr4) << std::endl;//4

decltype直接获取lambda原类型了,同样可以进行EBO,所以也是原始指针大小。

总结:

unique_ptr自定义deleter其实用最朴素的结构体仿函数式写法就很稳了,如果想用lambda的话,请使用decltype。

参考

  1. ^https://en.cppreference.com/w/cpp/language/ebo
  2. ^https://stackoverflow.com/questions/13503511/sizeof-of-stdfunctionvoidint-type

编辑于 2021-04-24 20:02

C / C++

C++

编程语言

理性发言,友善互动

2 条评论

李华

FileCloserLambda其实是default constructable,但是c++17不允许
std::unique_ptr<FILE, decltype(FileCloserLambda)> uptr4(fopen("test_file3.txt", "w"));
也就是必须要有第二个参数FileCloserLambda。


即使在c++17里
std::unique_ptr<FILE, decltype(FileCloserLambda)> uptr4(fopen("test_file3.txt", "w"), FileCloserLambda);
FileCloserLambda也没存进unique_ptr里(size没有变大),都能EBO,为什么不能直接删掉第二个参数。


c++20里好像是允许了
std::unique_ptr<FILE, decltype(FileCloserLambda)> uptr4(fopen("test_file3.txt", "w"));

2022-05-09

Interlock

需要给lambda起一个名字,好麻烦

2022-07-15

另外一篇文章

C++学习——从unique_ptr的deleter到[[no_unique_address]]

C++学习——从unique_ptr的deleter到[[no_unique_address]]

严格鸽

严格鸽

​​编辑

柚子厨/萝莉控/acm银

20 人赞同了该文章

首先呢,unique_ptr是有第二个参数的,不过这个参数大部分人都不怎么用。

可以自定义一个deleter

void test_uptr(){struct A{};auto My_Deleter = [](A * ptr) {std::cout<<"My Deleter\n";};std::unique_ptr<A,decltype(My_Deleter)> ptr1(new A(),My_Deleter);// No viable conversion from 'unique_ptr<[...], (lambda at /Users/mryange/test/test.cpp:57:23)>' to 'unique_ptr<[...], (default) // std::unique_ptr<A> ptr_2 = std::move(ptr1);
}

deleter的类型算在uptr的类型里面了,而shared_ptr

不是。

可以暂时理解成sptr是纯了一个类似std::function的东西作为deleter。

本文章的重点是,uptr如何保存这个deleter。

最简单的办法就是。

template<typename T ,typename Deleter>
struct Uptr{T *  ptr;Deleter deleter;
};

但是,显然,这不《0成本抽象》。

下面是msvc的实现。

_Zero_then_variadic_args_t这一坨是用来匹配不同的构造函数的,这里可以先不管

_Compressed_pair简化的样子。

template<typename Ty1 , typename Ty2 , bool = std::is_empty_v<Ty1> && !std::is_final_v<Ty1>>
struct Compressed_pair : private Ty1{Ty2 second ;Ty1 &  get_first() {return *this;}Ty2 & get_second(){return second;}
};
template<typename Ty1 , typename Ty2 >
struct Compressed_pair<Ty1,Ty2,false> {Ty1 first;Ty2 second ;Ty1 &  get_first() {return first;}Ty2 & get_second(){return second;}
};

这里用到了一个叫空集类优化的东西,网上的文章有很多我这里就不解释。

这里uptr把deleter放到了first,T*放到了second。

void test_Compressed_pair(){auto f = [](){std::cout<<"empty class f \n";};Compressed_pair<decltype(f), int * > pair_empty{};static_assert(sizeof(pair_empty) == 8);pair_empty.get_first()();struct NonEmptyCLass{NonEmptyCLass(int x) : _x(x){}int _x;void operator ()(){std::cout<<"non empty class \n";}};NonEmptyCLass g(114514);Compressed_pair<NonEmptyCLass, int * > pair_not_empty{g,nullptr};static_assert(sizeof(pair_not_empty) > 8);pair_not_empty.get_first()();}
empty class f
non empty class

当然了,这里还有个问题就是,如果frist是个final怎么办,不能继承了啊。

在C++20

中,标准提供了更好的一种写法。

template <typename Ty1, typename Ty2>
struct Compressed_no_unique_address {static_assert(std::is_empty_v<Ty1>);Ty2 second;[[no_unique_address]] Ty1 first;Ty1 &get_first() {return first;}Ty2 &get_second() {return second;}
};

测试

void test_Compressed_no_unique_address() {struct EmptyCLass final {};static_assert(sizeof(Compressed_pair<EmptyCLass, int *>) > 8);static_assert(sizeof(Compressed_no_unique_address<EmptyCLass, int *>) == 8);
}

也比原来的写法好看。。。

文章中出现的代码Compiler Explorer - C++ (x86-64 clang 17.0.1)

 

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

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

相关文章

【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 相关知识 测试说明 我的通关代码: 测试结果&#xff1a; 任务描述 本关任务&#xff1a;编写一个程序实现环形队列的基本运算。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a; 初始化队列、销毁队列、判断队列是否为空、进队列…

路由器、二层交换机与三层交换机的区别与应用

路由器、二层交换机和三层交换机是常见的网络设备&#xff0c;常常协同工作。它们都可以转发数据&#xff0c;但在功能、工作层级以及应用场景上存在差异。 1. 工作层级 三者在OSI模型中的工作层级不同&#xff1a; 路由器&#xff1a; 工作在 网络层&#xff08;第三层&#…

SQL计算字段:拼接字段

为了说明如何使用计算字段&#xff0c;本文将通过一个简单的示例来展示如何将两列组合成一个标题。假设Vendors表包含供应商的名称和国家信息&#xff0c;我们希望生成一个报表&#xff0c;其中列出每个供应商的名称和所在国家&#xff0c;并且需要格式化名称显示&#xff0c;国…

高级数据结构-树状数组

介绍 树状数组的推导 两个基础操作 模板-acwing795. 前缀和 #include<bits/stdc.h> using namespace std;const int N 1e610; int c[N]; int lowbit(int x){return x & -x; }int query(int x){int ans 0;for(; x; x - lowbit(x)) ans c[x];return ans; }void add…

香港科技大学广州|智能交通学域博士招生宣讲会—湖南大学专场

香港科技大学广州&#xff5c;智能交通学域博士招生宣讲会—湖南大学专场 &#x1f559;时间&#xff1a;2024年12月17日&#xff08;星期二&#xff09;15:00 &#x1f3e0;地点&#xff1a;湖南大学二办公楼三楼学生就业指导中心329 &#x1f517;报名链接&#xff1a;http…

node利用路由搭建web实例

npm init npm i express body-parser cookie-parser 封装web实例 搭建路由 导出web 应用实例注册

MFC案例:基于对话框的简易阅读器

一、功能目标&#xff1a; 1.阅读txt文件 2.阅读时可以调整字体及字的大小 3.打开曾经阅读过的文件时&#xff0c;能够自动从上次阅读结束的位置开始显示&#xff0c;也就是能够保存和再次使用阅读信息。 4.对于利用剪贴板粘贴来的文字能够存储成txt文件保存。 5.显示…

端点鉴别、安全电子邮件、TLS

文章目录 端点鉴别鉴别协议ap 1.0——发送者直接发送一个报文表明身份鉴别协议ap 2.0——ap1.0 的基础上&#xff0c;接收者对报文的来源IP地址进行鉴别鉴别协议ap 3.0——使用秘密口令&#xff0c;口令为鉴别者和被鉴别者之间共享的秘密鉴别协议ap 3.1——对秘密口令进行加密&…

电脑技巧:Everything 1.5 版本重大更新​支持拼音搜索+全文搜索

目录 一、软件介绍 二、主要更新亮点 更快的搜索速度和拼音搜索 全文搜索功能 智能推荐功能 增强的过滤选项 改进的用户界面 更好的多语言支持 增强的安全性和隐私保护 三、总结 Everything 作为一款备受推崇的文件搜索工具&#xff0c;以其卓越的性能和简洁的用户界…

element左侧导航栏

由element组件搭建的左侧导航栏 预览: html代码: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>首页</title><style> /*<!-- 调整页面背景颜色-->*/body{background-colo…

Datax可视化工具Datax-web安装部署

文章目录 一、Datax-web官网二、Datax-web介绍 1、Datax-web概述2、架构图3、系统环境要求4、特性支持 三、安装部署 1、环境准备2、Datax-web安装包准备 一、Datax-web官网 github&#xff1a;Datax-web gitee: Datax-web 二、Datax-web介绍 1、Datax-web概述 DataX Web…

node-js Express中间件

中间件介绍 什么是中间件 中间件其本质就是一个回调函数&#xff0c;可以像路由一样访问请求对象&#xff08;request&#xff09;和响应对象&#xff08;response&#xff09;。中间件的作用是什么 通过函数封装公共操作&#xff0c;简化代码中间件类型 - 全局中间件 - 路由中…

【数学】矩阵的逆与伪逆 EEGLAB

文章目录 前言matlab代码作用EEGLAB 中的代码总结参考文献 前言 在 EEGLAB 的使用中&#xff0c;运行程序时出现了矩阵接近奇异值&#xff0c;或者缩放错误。结果可能不准确。RCOND 1.873732e-20 的 bug&#xff0c;调查 EEGLAB 后发现是 raw 数据的问题。 matlab代码 A_1 …

TCP 的三次握手与四次挥手

TCP 的三次握手与四次挥手 TCP 协议&#xff0c;使用 TCP 的三次握手建立连接&#xff0c;使用四次挥手断开连接。 我们先看看基本的计算机网络中的TCP连接建立和断开的过程。 1.HTTP 三次握手 HTTP 的三次握手过程如下&#xff1a; 第一次握手&#xff08;SYN&#xff09;…

解锁前端开发速度的秘密武器【Vite】

在前端开发的江湖中&#xff0c;有人偏爱 Webpack 的强大与稳定&#xff0c;有人钟情于 Rollup 的轻量与高效。而 Vite&#xff0c;这个后来居上的工具&#xff0c;却以“极致的快”和“极简的易”赢得了开发者的芳心。众所周知万事都有缘由&#xff0c;接下来我们就来深度剖析…

【动态库.so | 头文件.hpp】基于CMake与CMakeList编写C++自定义库

前言 最近比较忙&#xff0c;其他系列教程得等到年后一起更&#xff01;请大家多多包涵&#xff01;&#xff01;相信各位在配置C环境和各类库的时候一定经常看到如下小连招 git clone https://github.com/opencv/opencv.git cd opencv mkdir build && cd build cma…

前端编辑器JSON HTML等,vue2-ace-editor,vue3-ace-editor

与框架无关 vue2-ace-editor有问题&#xff0c;ace拿不到&#xff08;brace&#xff09; 一些组件都是基于ace-builds或者brace包装的 不如直接用下面的&#xff0c;不如直接使用下面的 <template><div ref"editor" class"json-editor"><…

SpringMVC框架——入门

目录 一、三层框架和MVC 1. 三层架构 2. MVC模型 二、SpringMVC入门案例 1. SpringMVC的概述 2. SpringMVC的入门程序 3. 入门案例的执行过程分析 4. RequestMapping注解 一、三层框架和MVC 1. 三层架构 开发服务器端程序&#xff0c;一般都基于两种形式&#xff0c;一…

港口智慧应急管理平台:应急先锋,港航卫士

在风云变幻的港航世界&#xff0c;安全是永恒的基石。港口智慧应急管理平台宛如一位无畏的先锋&#xff0c;以智能化、数字化、信息化为利刃&#xff0c;借助 AI 与大数据的神奇力量&#xff0c;为港口保驾护航。 传统应急管理往往在事故发生后被动响应&#xff0c;而此平台却…

Prime2_解法二:openssl解密凭据

Prime2_解法二&#xff1a;openssl解密凭据 本博客提供的所有信息仅供学习和研究目的&#xff0c;旨在提高读者的网络安全意识和技术能力。请在合法合规的前提下使用本文中提供的任何技术、方法或工具。如果您选择使用本博客中的任何信息进行非法活动&#xff0c;您将独自承担全…