本文始发于公众号【雨乐聊编程】,地址:性能大杀器:智能指针的资源管理
你好,我是雨乐!
想起几年前,开始用Modern 新特性的第一件事就是使用智能指针
替代传统的裸指针,传统的裸指针虽然强大,但容易出错,容易导致内存泄漏和悬挂指针等问题。幸运的是,Modern C++ 标准库提供了一种强大而灵活的工具来解决这些问题:智能指针。智能指针是一种高级数据结构,可以自动管理动态分配的内存,并在不再需要时释放它。
本文并不是来介绍智能指针原理等(如有需要,请参阅文章xx),而是聊聊其中的一个功能自定义删除器。
原因
可能有人会说,智能指针不是有默认的删除器么,为什么还需要自定义?
一般来说,有这种观点的人不是少数,正如一句俗语所说:存在即合理。既然标准库提供了自定义,必然有其原因,在这里,我总结下自定义删除器存在原因:
- 在删除对象之前,需要一些辅助性操作,比如打印或者通知等
- 对于自定义的内存分配器(参考文章xxx)
- 多语言混合编程
- 一种特殊的对象,比如说文件指针FILE*,这种就需要我们自定义fclose()作为删除器
- …
智能指针
为了
std::unique_ptr
针对单个对象声明如下:
template< class T, class Deleter = std::default_delete<T> >
class unique_ptr;
T
是指向的对象的类型Deleter
是用于销毁对象的删除器类型,默认情况下是std::default_delete<T>
针对数组类型声明如下:
template < class T, class Deleter>
class unique_ptr<T[], Deleter>;
这个声明定义了另一个模板类 std::unique_ptr
,用于管理数组对象的内存。与前一个声明相比,这个声明不使用默认删除器,因为在处理数组时,通常需要特定的删除器来正确释放内存。
std::shared_ptr
cppreference对其声明之一如下:
template< class Y, class Deleter >
shared_ptr( Y* ptr, Deleter d );
与std::unique_ptr不同的是,std::shared_ptr将删除器作为其构造函数的参数之一,有以下几个原因:
- std::unique_ptr 的删除器作为模板参数:
std::unique_ptr
的设计目标是实现独占拥有权,即在任意时刻只能有一个std::unique_ptr
拥有某个对象。由于其设计初衷是管理单个对象的内存,因此在实例化时就确定了删除器类型。这种设计使得编译器可以对std::unique_ptr
类型进行优化,因为删除器类型是确定的,不会引入额外的运行时开销。 - std::shared_ptr 的删除器作为变量:
std::shared_ptr
的设计目标是实现共享所有权,即多个指针可以共享同一块内存。由于可能有多个std::shared_ptr
指向同一对象,每个指针可能需要不同的删除器来销毁对象。因此,将删除器作为模板参数在这种情况下是不合适的,因为无法为每个std::shared_ptr
指定不同的删除器。相反,将删除器作为std::shared_ptr
的构造函数参数或成员变量,可以在每个std::shared_ptr
实例化时指定不同的删除器,从而灵活地满足不同的需求。
总的来说,std::unique_ptr
和 std::shared_ptr
的设计选择与其不同的所有权语义和用途密切相关。std::unique_ptr
的删除器作为模板参数可以实现更高效的编译时优化,而 std::shared_ptr
的删除器作为变量则允许在运行时动态指定不同的删除器。
std::default_delete
在前面内容中,多次提到了std::default_delete
,那么这个到底是什么?这是一个 默认删除器,当我们在使用 智能指针的时候,如果没有显式指定删除器,则默认使用这个,所以称之为默认删除器
。
其定义在头文件<memory>
中,声明如下:
template< class T > struct default_delete;
template< class T > struct default_delete<T[]>;
第一个非特化版主要针对单个对象
第二个是特化版,针对对象数组
成员函数
构造函数
默认构造函数:
constexpr default_delete() noexcept = default; // default
这个声明表示 std::default_delete
的默认构造函数,默认情况下是 noexcept
的。它使用 = default
来指示编译器生成默认的构造函数实现,这意味着该构造函数不会做任何额外的工作,仅仅是默认的行为。这个构造函数通常用于创建默认删除器实例。
拷贝构造函数:
template <class U>
default_delete( const default_delete<U>& d ) noexcept;
这个声明表示 std::default_delete
的复制构造函数,它接受一个类型为 std::default_delete<U>
的参数,并生成一个相同类型的删除器。这个构造函数通常用于在不同类型的智能指针之间共享相同的删除器。
用例
例1:
{
std::unique_ptr<int> ptr(new int(5));
}
ptr使用默认删除器即std::default_delete<int>
例2:
{
std::unique_ptr<int[]> ptr(new int[10]);
}
ptr使用默认删除器即std::default_delete<int[]>
例3:
std::vector<int*> v;
for(int n = 0; n < 100; ++n)
v.push_back(new int(n));
std::for_each(v.begin(), v.end(), std::default_delete<int>());
在std::for_each中,显式指定使用默认删除器std::default_delete<int>
例4:
std::shared_ptr<int> shared_bad(new int[10]);
ptr使用默认删除器即std::default_delete<int>
例5:
shared_ptr<int[]> ptr(new int[10]);
ptr使用默认删除器即std::default_delete<int[]>
,不过需要注意的是自C++17起,shared_ptr支持数组类型。
自定义删除器
自定义删除器往往支持以下几种类型:
- std::function
- 函数指针
- 有状态/无状态的仿函数或者lambda
因为自定义删除器在std::shared_ptr和std::unique_ptr上使用方式不同,因此单独针对这两种指针进行示例分析。
针对std::shared_ptr
由于C++17之前的编译器不支持数组类型的默认删除器,因此,我们可以手动实现该功能。
使用仿函数
template< typename T >
struct ArrayDeleter
{
void operator ()( T const * p)
{
delete[] p;
}
};
std::shared_ptr<int> sp(new int[10], ArrayDeleter<int>());
使用lambda
std::shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });
默认删除器
此种方式仅针对C++17及以上版本:
std::shared_ptr<int> sp(new int[10], std::default_delete<int[]>());
针对std::unique_ptr
较std::shared_ptr使用自定义删除器相比,std::unique_ptr要复杂的多,本节的示例仅针对常见场景。
struct Obj {
// sth
};
void ObjDeleter(Obj *ptr) {
delete ptr;
}
struct DeleterFunctor {
void operator()(Obj* ptr) {
// ...
}
};
使用std::function
std::unique_ptr<Obj, std::function<void (Obj*)>> u1(new Obj, ObjDeleter);
或者
std::unique_ptr<Obj, decltype(&ObjDeleter)> u2(new Obj, ObjDeleter);
使用函数指针
std::unique_ptr<Obj, void (*)(Obj *)> u3(new Obj, ObjDeleter);
使用仿函数
std::unique_ptr<Obj, DeleterFunctor> u4(new Obj);
或者
DeleterFunctor functor;
std::unique_ptr<Obj, DeleterFunctor&> u5(new Obj, functor );
使用lambda
auto del = [](Obj*){ ... }
std::unique_ptr<Obj, void (*)(Obj *)>> u6(new Obj, del);
std::unique_ptr<Obj, decltype(del)>> u7(new Obj, del);
局限性
前面内容中已经提到过,删除器作为模板参数(std::unique_ptr)或者作为构造函数其中一个参数(std::shared_ptr),因此对于使用std::make_shared
或者std::make_unique
方式创建的对象,不能使用自定义删除器,当然了,网上也有一些方式,不过感觉太复杂了,有时候不要为了用而用,当一种实现方式非常麻烦的时候,就需要考虑是否该换一种实现方式了。