C++类型擦除

以线程池举例
线程池需要接收要执行的任务,这些任务需要形成一个队列
任务可以是函数,lambda,重载括号运算符的类
那么在定义一个数组去保存这些任务该如何定义?

class my_thread {
    using task_type = void(*)();//相当于typedef别名
    my_queue<task_type> task_queue;
};

那这个队列可以存储函数指针来形成任务队列,且这些函数返回值为void不带参数

那对于重载了括号运算符的类如何放进去?
定义一个基类,任务继承该基类,运行时多态

struct task_base {
    virtual ~task_base() = 0;//析构函数要定义为虚函数
    virtual void run() const = 0;
};

// 用户编写的具体任务类
struct task_imple : public task_base { 
    void run() const override {
        // 运算...
    }
};

// 使用:
std::unique_ptr<task_base> pt = std::make_unique<task_imple>();
pt->run();

对于lambda函数,C++11的lambda有一个非常关键点要记住,编译器会给每一个lambda赋予独一无二的类型,没看错,是类型

auto lambda0 = []() { std::cout << "lambda0"; };
auto lambda1 = []() { std::cout << "lambda1"; };

这两个lambda的类型可以理解为都是编译器临时生成的,互不相同,不信你可以用std::is_same检查
所以my_queue<???> task_queue;中问号处不知道写什么

using type0 = decltype(lambda0); 
using type1 = decltype(lambda1);
static_assert(std::is_same<type0, type1>::value == false);

我们想要一个下面这种封装好的类

struct my_task {
    template <typename F>
    my_task(F&& f); // 模板化的构造函数,
                    // 从任意类型的函数对象构建任务

    void operator()() const; // 没有显式的虚函数调用,
                             // 不用指针操作而用值语义
};

// 使用:
my_task t{ /* 任意的函数对象 */ }; // 用户不用填写函数对象的具体类型
                                  // 由编译器推导
t();  

std::function横空出世
C++语境下的类型擦除,技术上来说,是编写一个类,它提供模板的构造函数和功能
隐藏对象的具体类型,保留其行为
可以将函数,lambda以及重载了括号运算符的类再封装一层,使其功能一致,看不出区别了

也就是库作者把面向对象的代码写了,而不是推给用户写

struct task_base {
   virtual ~task_base() {}
   virtual void operator()() const = 0;
};

template <typename F>
struct task_model : public task_base {
    F functor_;

    template <typename U> // 构造函数是函数模板
    task_model(U&& f) :
      functor_(std::forward<U>(f)) {}

    void operator()() const override {
        functor_();
    }
};

初始动机是用一个类型包装不同的函数对象。然后,考虑这些函数对象需要提供的功能(affordance),此处为使用括号运算符进行函数调用。
最后,把这个功能抽取为一个接口,此处为my_task,我们在在这一步擦除了对象具体的类型。
这便是类型擦除的本质:切割类型与其行为,使得不同的类型能用同一个接口提供功能。

以上参考https://zhuanlan.zhihu.com/p/351291649
整理以备快速拾起

posted @ 2023-01-26 22:27  ecnu_lxz  阅读(124)  评论(0编辑  收藏  举报