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)

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AGCRN论文解读

一、创新点 传统GCN只能基于静态预定义图建模全局共享模式&#xff0c;而AGCRN通过两种GCN的增强模块&#xff08;NAPL、DAGG&#xff09;实现了更精细的节点特性学习和图结构生成。 1 节点自适应参数学习模块&#xff08;NAPL&#xff09; 传统GCN通过共享参数&#xff08;权重…

使用观测云排查数据库死锁故障

故障发现 核心应用 pod 发生重启&#xff0c;同时接收到对应使用者反馈业务问题&#xff0c;开始排查。 观测云排查现场 1、根据重启应用信息&#xff0c;查询 APM 执行数据库 update 操作大量报错&#xff0c;执行时间在 5min 以上。 分析 APM 链路异常&#xff0c;发现是触…

UNIX数据恢复—UNIX系统常见故障问题和数据恢复方案

UNIX系统常见故障表现&#xff1a; 1、存储结构出错&#xff1b; 2、数据删除&#xff1b; 3、文件系统格式化&#xff1b; 4、其他原因数据丢失。 UNIX系统常见故障解决方案&#xff1a; 1、检测UNIX系统故障涉及的设备是否存在硬件故障&#xff0c;如果存在硬件故障&#xf…

npm或yarn包配置地址源

三种方法 1.配置.npmrc 文件 在更目录新增.npmrc文件 然后写入需要访问的包的地址 2.直接yarn.lock文件里面修改地址 简单粗暴 3.yarn install 的时候添加参数 设置包的仓库地址 yarn config set registry https://registry.yarnpkg.com 安装&#xff1a;yarn install 注意…

opencv——图片矫正

图像矫正 图像矫正的原理是透视变换&#xff0c;下面来介绍一下透视变换的概念。 听名字有点熟&#xff0c;我们在图像旋转里接触过仿射变换&#xff0c;知道仿射变换是把一个二维坐标系转换到另一个二维坐标系的过程&#xff0c;转换过程坐标点的相对位置和属性不发生变换&a…

【学习】企业通过CMMI认证,还需要申请CSMM资质吗

​ 企业通过CMMI认证之后&#xff0c;是否还有必要申请CSMM资质&#xff1f;这是一个值得软件企业深思的问题。虽然CMMI和CSMM都在组织的软件过程改进和认证方面发挥着重要作用&#xff0c;但它们各自拥有自己的特点在。企业需要根据自身发展需求来选择适合的认证方式。首先我…

OpenHarmony-3.HDF input子系统(5)

HDF input 子系统OpenHarmony-4.0-Release 1.Input 概述 输入设备是用户与计算机系统进行人机交互的主要装置之一&#xff0c;是用户与计算机或者其他设备通信的桥梁。常见的输入设备有键盘、鼠标、游戏杆、触摸屏等。本文档将介绍基于 HDF_Input 模型的触摸屏器件 IC 为 GT91…

BurpSuite之移动端流量抓包

学习视频来自B站UP主泷羽sec&#xff0c;如涉及侵权马上删除文章。 笔记只是方便学习&#xff0c;以下内容只涉及学习内容&#xff0c;切莫逾越法律红线。 安全见闻&#xff0c;包含了各种网络安全&#xff0c;网络技术&#xff0c;旨在明白自己的渺小&#xff0c;知识的广博&a…

Any2Policy: Learning Visuomotor Policy with Any-Modality(类似AnyGPT)

发表时间&#xff1a;NeurIPS 2024 论文链接&#xff1a;https://readpaper.com/pdf-annotate/note?pdfId2598959255168534016&noteId2598960522854466816 作者单位&#xff1a;Midea Group Motivation&#xff1a;Current robotic learning methodologies often focus…

QTreeView 与 QTreeWidget 例子

1. 先举个例子 1班有3个学生&#xff1a;张三、李四、王五 4个学生属性&#xff1a;语文 数学 英语 性别。 语文 数学 英语使用QDoubleSpinBox* 编辑&#xff0c;范围为0到100,1位小数 性别使用QComboBox* 编辑&#xff0c;选项为&#xff1a;男、女 实现效果&#xff1a; 2…

计算机视觉与医学的结合:推动医学领域研究的新机遇

目录 引言医学领域面临的发文难题计算机视觉与医学的结合&#xff1a;发展趋势计算机视觉结合医学的研究方向高区位参考文章结语 引言 计算机视觉&#xff08;Computer Vision, CV&#xff09;技术作为人工智能的重要分支&#xff0c;已经在多个领域取得了显著的应用成果&…

谷粒商城—分布式基础

1. 整体介绍 1)安装vagrant 2)安装Centos7 $ vagrant init centos/7 A `Vagrantfile` has been placed in this directory. You are now ready to `vagrant up` your first virtual environment! Please read the comments in the Vagrantfile as well as documentation on…

麒麟系统+达梦数据库+MybatisPlus+Redis+SpringBoot

环境准备 麒麟系统 在麒麟系统官网进行下载镜像 这里选择的是麒麟V10桌面版&#xff0c;使用虚拟机启动 修改root密码 # 启动到单用户模式 init 1 # 修改 root 密码 passwd root # 重启 reboot达梦数据库准备 进入达梦官网 我这里选择的是达梦数据库管理系统DM8开发版 下…

DFC:控制 ~~到达率~~ 最小化等待时间

DFC&#xff1a;控制 到达率 最小化等待时间 计算节点的等待成本&#xff1a;公式&#xff08;2&#xff09; ( λ i λ ( W q i C i μ c i ‾ ) ) (\frac{\lambda_i}{\lambda}(W_q^i C_i\overline{\mu_c^i})) (λλi​​(Wqi​Ci​μci​​)) 在这个到达率下的等待时间&am…

单词翻转

单词翻转 C语言实现C实现Java实现Python实现 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 输入一个句子&#xff08;一行&#xff09;&#xff0c;将句子中的每一个单词翻转后输出。 输入 只有一行&#xff0c;为一个字符串&#xff0c…

数据分析实战—房价特征关系

1.实战内容 &#xff08;1&#xff09; 读取房价特征关系表&#xff08;house_price.npz&#xff09;绘制离地铁站的距离与单位面积的房价的散点图&#xff0c;并对其进行分析&#xff1b; import pandas as pd import numpy as np import warnings warnings.filterwarnings(&…

网页502 Bad Gateway nginx1.20.1报错与解决方法

目录 网页报错的原理 查到的502 Bad Gateway报错的原因 出现的问题和尝试解决 问题 解决 网页报错的原理 网页显示502 Bad Gateway 报错原理是用户访问服务器时&#xff0c;nginx代理服务器接收用户信息&#xff0c;但无法反馈给服务器&#xff0c;而出现的报错。 查到…

Linux入门攻坚——41、Linux集群系统入门-lvs(2)

lvs-dr&#xff1a;GATEWAY Director只负责请求报文&#xff0c;响应报文不经过Director&#xff0c;直接由RS返回给Client。 lvs-dr的报文路线如上图&#xff0c;基本思路就是报文不会回送Director&#xff0c;第①种情况是VIP、DIP、RIP位于同一个网段&#xff0c;这样&…

【Python网络爬虫笔记】10- os库存储爬取数据

os库的作用 操作系统交互&#xff1a;os库提供了一种使用Python与操作系统进行交互的方式。使用os库来创建用于存储爬取数据的文件夹&#xff0c;或者获取当前工作目录的路径&#xff0c;以便将爬取的数据存储在合适的位置。环境变量操作&#xff1a;可以读取和设置环境变量。在…

在CentOS中安装和卸载mysql

在CentOS7中安装和卸载mysql 卸载mysql1、查看是否安装过mysql2、查看mysql服务状态3、关闭mysql服务4、卸载mysql相关的rpm程序5、删除mysql相关的文件6、删除mysql的配置文件my.cnf 安装mysql1、下载mysql相关的rpm程序2、检查/tmp临时目录权限3、安装mysql前的依赖检查3、安…