Bootstrap

(C++) 智能指针指定删除器

⌚前言

自C++11后,推出了三个智能指针。其中 unique_ptrshared_ptr可以指定删除器。

但两者的形式却不太一样,本文将带你了解两者的基础使用区别。

⏲️注意

weak_ptr 是专门用来作为共享指针的使用权的,是一种零时的所有权,是没有删除器的。

make_xxx 是专门用于创建的,参数也很简单,就是直接转发到构造函数中,不附带指定删除器。(但不排除以后可能会添加兼容删除器的版本,但个人认为实现起来会比较麻烦)

本文不考虑数组版本的特化。

⌚unique_ptr

⏲️说明

类型声明

template<
    class T,
    class Deleter = std::default_delete<T>
> class unique_ptr;

构造声明

unique_ptr( pointer p, ? d) noexcept;
unique_ptr( pointer p, ? d) = delete;

构造拥有 p 的 std::unique_ptr 对象,以 p 初始化存储的指针,并按下列方式初始化删除器 D(依赖于 D 是否为引用类型)。

a) 若 D 是非引用类型 A,则签名是:

unique_ptr(pointer p, const A& d) noexcept; // (1)	(要求 Deleter 为不抛出可复制构造 (CopyConstructible) )
unique_ptr(pointer p, A&& d) noexcept;      // (2)	(要求 Deleter 为不抛出可移动构造 (MoveConstructible) )

b) 若 D 是左值引用类型 A&,则签名是:

unique_ptr(pointer p, A& d) noexcept;
unique_ptr(pointer p, A&& d) = delete;

c) 若 D 是左值引用类型 const A&,则签名是:

unique_ptr(pointer p, const A& d) noexcept;
unique_ptr(pointer p, const A&& d) = delete;

所有情况下删除器从 std::forward<decltype(d)>(d) 初始化。这些重载只有在 std::is_constructible<D, decltype(d)>::value 为 true 时才会参与重载决议。


说白了,就是模板的第二个删除器的参数类型要和构造函数的类型匹配。

比如,模板参数是左值引用A&,那构造函数也必须是左值引用A&

⏲️实例

其实只要知道原理,熟悉模板和函数对象的朋友很容易能构造出实例。

基于仿函数

#include <iostream>
#include <memory>

struct MyDeleter {
    void operator()(int* p) const {
        std::cout << __func__ << std::endl;
        delete p;
    }
};

int main() {
    MyDeleter del;

    std::unique_ptr<int, MyDeleter> p0(new int{100}, del);
    std::unique_ptr<int, MyDeleter> p1(new int{100}, MyDeleter{});
}

基于函数指针

#include <iostream>
#include <memory>

void del_fun(int* p) {
    std::cout << __func__ << std::endl;
    delete p;
}

int main() {
    std::unique_ptr<int, void (*)(int*)> p0(new int(100), [](int* ptr) {
        std::cout << __func__ << std::endl;
        delete ptr;
    });

    std::unique_ptr<int, void (*)(int*)>     p1(new int(100), del_fun);
    std::unique_ptr<int, decltype(&del_fun)> p2(new int(100), del_fun);
}

删除器为引用类型

#include <iostream>
#include <memory>

struct MyDeleter {
    // const 限定
    void operator()(int* p) const {
        std::cout << __func__ << std::endl;
        delete p;
    }
};

int main() {
    //! 指定为非引用类型
    std::unique_ptr<int, MyDeleter> p0(new int{100}, MyDeleter{});

    //! 指定为左值引用
    MyDeleter                        del;
    std::unique_ptr<int, MyDeleter&> p1(new int{100}, del);

    //! 指定类型为常引用
    // 注意 unique_ptr(pointer p, const A&& d) = delete;
    std::unique_ptr<int, const MyDeleter&> p2(new int{100}, del);

    //! 指定为右值应用
    // 错误,不能是右值引用
    // std::unique_ptr<int, MyDeleter&&> p3(new int{100}, MyDeleter{});
}

⌚shared_ptr

⏲️说明

类型声明

template< class T > class shared_ptr;

构造声明

当然shared_ptr的带有删除器的构造不止这一种,但是该构造是最具典型和功能单一的。

template< class Y, class Deleter >
shared_ptr( Y* ptr, Deleter d );

以指定的删除器 d 为删除器。表达式 d(ptr) 必须良构,拥有良好定义行为且不抛异常。d 的构造和从它复制并存储的删除器的构造必须不抛异常。


总之shared_ptr的删除器不受类型干扰,而是在构造的时候确定,给了用户极大的自由度。

⏲️实例

可以方便的拷贝

#include <iostream>
#include <memory>

struct MyDeleter {
    void operator()(int* p) const {
        std::cout << __LINE__ << __func__ << std::endl;
        delete p;
    }
};

struct YourDeleter {
    void operator()(int* p) const {
        std::cout << __LINE__ << __func__ << std::endl;
        delete p;
    }
};

int main() {
    MyDeleter   my_del;
    YourDeleter your_del;

    std::shared_ptr<int> sp0(new int{100}, my_del);
    std::cout << *sp0 << std::endl;
    std::shared_ptr<int> sp1(new int{200}, your_del);
    std::cout << *sp1 << std::endl;

    // 类型是一样的,走拷贝赋值
    sp0 = sp1;
}

可以方便的由容器管理

#include <iostream>
#include <memory>
#include <vector>

struct MyDeleter {
    void operator()(int* p) const {
        std::cout << __LINE__ << __func__ << std::endl;
        delete p;
    }
};

struct YourDeleter {
    void operator()(int* p) const {
        std::cout << __LINE__ << __func__ << std::endl;
        delete p;
    }
};

int main() {
    std::shared_ptr<int> sp0(new int{100}, MyDeleter{});
    std::shared_ptr<int> sp1(new int{200}, YourDeleter{});

    // 统一使用vector进行管理
    std::vector<std::shared_ptr<int>> sp_arr;
    sp_arr.push_back(sp0);
    sp_arr.push_back(sp1);

    for (auto sp : sp_arr) {
        std::cout << *sp << std::endl;
    }
}

⌚拓展

⏲️函数类型 & 函数指针类型

#include <iostream>

int main() {
    // 指针类型
    using T0 = decltype(&main);
    // 函数类型
    using T1 = decltype(main);

    // 函数指针
    std::cout << typeid(&main).name() << std::endl;
    // 函数类型
    std::cout << typeid(main).name() << std::endl;
}

gcc mingw

PFivE
FivE

msvc

int (__cdecl*)(void)
int __cdecl(void)

⌚END

🌟视频讲解

在这里插入图片描述

🌟关注我

关注我,学习更多C/C++,算法,计算机知识

B站:

👨‍💻主页:天赐细莲 bilibili

;