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

无意间看到

FOCUS:现代 C++:一文读懂智能指针​zhuanlan.zhihu.com

https://zhuanlan.zhihu.com/p/150555165

昂,一个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(空基类优化) 从对象布局中优化掉。

第二个:

	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>)看它也可以发现它是需要一定的内存开销的(https://stackoverflow.com/questions/13503511/sizeof-of-stdfunctionvoidint-type) 。而把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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用unique_ptr和shared_ptrC++中管理动态内存的两种常用方式。 1. unique_ptrunique_ptr是一种独占式智能指针,它提供了对动态分配对象的独占所有权。当unique_ptr超出作用域或被显式释放时,它所管理的对象会被自动销毁。 使用unique_ptr的步骤如下: (1)包含头文件:`#include <memory>` (2)创建unique_ptr对象并初始化:`std::unique_ptr<T> ptr(new T);`,其中T是所管理对象的类型。 (3)通过箭头操作符(->)或解引用操作符(*)访问所管理的对象。 示例代码: ```cpp #include <memory> int main() { std::unique_ptr<int> ptr(new int(5)); std::cout << *ptr << std::endl; // 输出:5 *ptr = 10; std::cout << *ptr << std::endl; // 输出:10 return 0; } ``` 2. shared_ptr: shared_ptr是一种共享式智能指针,它允许多个指针共同拥有和管理同一个对象。当最后一个shared_ptr超出作用域或被显式释放时,它所管理的对象会被自动销毁。 使用shared_ptr的步骤如下: (1)包含头文件:`#include <memory>` (2)创建shared_ptr对象并初始化:`std::shared_ptr<T> ptr(new T);`,其中T是所管理对象的类型。 (3)通过箭头操作符(->)或解引用操作符(*)访问所管理的对象。 示例代码: ```cpp #include <memory> int main() { std::shared_ptr<int> ptr1(new int(5)); std::shared_ptr<int> ptr2 = ptr1; std::cout << *ptr1 << std::endl; // 输出:5 std::cout << *ptr2 << std::endl; // 输出:5 *ptr1 = 10; std::cout << *ptr1 << std::endl; // 输出:10 std::cout << *ptr2 << std::endl; // 输出:10 return 0; } ``` 需要注意的是,shared_ptr使用引用计数来跟踪有多少个指针共享同一个对象。当最后一个指针超出作用域或被显式释放时,引用计数减少到0时,对象才会被销毁。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值