当前位置: 首页 > article >正文

std::function的简易实现

本节我们来实现一个简易的std::function

我们知道std::function是用来包装可调用对象的,在C++中,可调用对象包括 普通函数lambda表达式重载了()操作符的类对象类静态函数类成员函数这几类。

C++程序的编译顺序:预处理(xxx.i) 编译(xxx.s) 汇编(xxx.obj) 链接(xxx.exe / linux可执行文件)
模板类(函数)的实例化,是在 预处理之后,编译之前;所以模板并不是真正可执行的C++代码,应该将它理解成一个模具,我们使用什么样的材料(模板参数),就会生成什么样的代码;

模板类(函数)的模板参数,其实只是一个类型占位符;我们可以赋予这个占位符任何意义,模板类(函数)的实现也是基于这个我们赋予的意义来进行编写的;所以在使用模板类(函数)时,只要传入的模板参数能满足我们赋予的意义,这个模板类(函数)就能正常运行

模板函数的模板参数是可以使用编译器自动推导,而模板类的模板参数,却需要显示指定,无法使用自动推导


首先回顾一下 std::function的使用,举个比较简单的例子:

	int func(int x, int y) { return x + y; }
	std::function<int, (int, int)> f1(func);		//使用1
	std::function<int, (int, int)> f2([int x, int y](){ return x + y; });	//使用2

	class Test
	{
		int memFunc(int x, int y) { return x + y; }
	}
	Test t;
	std::function<int, (int, int)> f2(&Test::memFunc, &t);		//使用3

可以看到,我们给std::function传递模板参数时,模板参数的形式为 ReturnType (Params)
观察使用1 使用2可以发现,构造函数的参数形式为一个可以被执行的函数(1中为普通函数,2中为lambda表达式)

结合我们以上发现的特点,最容易想到形式是:
模板参数传递 返回值类型 参数包, 构造函数传递一个可执行的函数; 然后重载 ()操作符,将参数包转发到构造函数传递的可调用函数中去,执行一下该函数即可;

//代码段1
template <typename Ret, typename... Args>
struct myfunction
{
    template <typename Functor>
    myfunction(Functor&& func)
    {
    	// 将 func 存储到 m_func 中, 
    	// m_func(std::forward<Functor>(func));
    }

    Ret operator()(Args&&... args)
    {
    	//调用 m_func 即可
//        return m_func(std::forward<Args>(args)...);
    }
};

这里其实存在一个问题,那就是我们其实并没有这个m_func成员变量;这个成员变量的类型应该是和构造函数的模板参数一个类型(Functor类型),但是这个Functor只对构造函数可见,所以我们没办法定义一个 Functor m_func;的成员变量;

那怎么解决?

自然而然就会想到,将 Functor模板参数从构造函数的模板参数,移动到类模板参数中去,扩大了作用域;自然构造函数就不再是模板函数

//代码段2
template <typename Functor, typename Ret, typename... Args>
struct myfunction
{
   myfunction(Functor&& func)
   {
       m_func(std::forward<Functor>(func));
   }

   Ret operator()(Args&&... args)
   {
       return m_func(std::forward<Args>(args)...);
   }
   Functor m_func;
};

这样做固然可以,但是使用时我们是不是得传递这个模板类的模板参数,还是使用以上的例子,我们现在必须得这样使用这个模板类 myfunction<decltype(func), int (int,int)> f;, 其中第一个模板参数Functor, 需要我们自己显示指定;这显然会加大使用负担,且与std::function<int (int, int)>的形式不符;

结合以上的思考,将第一个模板参数Functor放到构造函数中去,由编译器进行推导,而不是定义myfunction时显示指定类型,这是比较合理的,也更符合使用习惯;所以我们还是要在代码段1的基础上进行改进;
现在问题变成了: 怎样才能让代码段1支持存储Functor类型的成员变量呢?

有了以上的思考,我们知道,将模板参数的作用域从函数提升到中,就可以扩大可见性
我们可以构造一个辅助类,将构造函数的模板参数变成辅助类的类模板参数,这样就可以将辅助类的类模板参数存储在辅助类中,然后想办法在myfunction中存储辅助类的指针或对象,在myfunction重载的()中调用存储在辅助类中的可调用函数,代码如下:

template <typename Ret, typename... Args>
struct myfunction
{
    template <typename Functor>
    myfunction(Functor&& func)
    {
    	m_pCallAble = new CallAble<Functor>(std::forward<Functor>(func));
    }

    Ret operator()(Args&&... args)
    {
    	if (m_pCallAble != nullptr)
    	{
    		return m_pCallAble->invoke(std::forward<Args>(args)...);
    	}
    }

	//辅助类基类
    struct ICallAble
    {
        virtual ~ICallAble() = default;
        virtual Ret invoke(Args&&... args) = 0;
    };
    
	//辅助类, 用于存储myfunction构造函数中的 可执行函数
	template <typename Functor>
	struct CallAble
	{
		CallAble(Functor&& func) : m_func(func) {}
		Ret invoke(Args&&... args) override
		{
			return m_func(std::forward<Args>(args)...);
		}
		Functor m_func;
	};

	ICallAble* m_pCallAble = nullptr;
};

OK,以上代码我们成功将myfunction构造函数的参数存储到了 m_pCallAble 变量中,到这里为止,我们这个myfunction类已经可以包装 普通函数 lambda表达式 重载了()的类对象 类静态函数
但是还无法包装类成员函数,我们观察最初的例子中的 形式3,不难发现这种形式的构造函数需要传递一个可调用函数,以及一个类对象, 所以我们还需要给myfunction加一个重载的构造函数

    template<typename Functor, typename Class>
    myfunction(Functor&& func, Class* obj)
    {
    }

当然我们可以继续使用 m_pCallAble成员变量,此时 new的就不再是 CallAble类对象了,因为这个类只能存储Functor模板参数,无法存储Class模板参数;所以我们继续派生自 ICallAble,写一个可以存储这两个参数的模板类

	//重载的构造函数
    template<typename Functor, typename Class>
    myfunction(Functor&& func, Class* obj)
    {
    	m_pCallAble = new MemCallAble<Functor, Class>(std::forward<Functor>(func), obj);
    }
  
	//包装类成员函数的派生类
    template <typename Functor, typename Class>
    struct MemCallAble : public ICallAble
    {
        MemCallAble(Functor&& func, Class* obj): m_func(std::forward<Functor>(func)), m_obj(obj)
        {
        }

        Ret invoke(Args&&... args) override
        {
            return (m_obj->*m_func)(std::forward<Args>(args)...);
        }

        Functor m_func;
        Class* m_obj = nullptr;
    };

这样我们的myfunction就可以类似形式3,来包装类成员函数了,到此实现了基本功能, 贴出以上思考后的代码:

template <typename T>
class myfunction;

template <typename Ret, typename... Args>
class myfunction<Ret(Args...)>
{
public:
    myfunction() {}

    template <typename Function>
    myfunction(Function&& func) : m_ptr { new CallAble<Function>(std::forward<Function>(func)) } {}

    template <typename Function, typename Class>
    myfunction(Function&& func, Class* obj) : m_ptr { new MemCallAble<Function, Class>(std::forward<Function>(func), obj) } {}

    myfunction(const myfunction& rhs) { m_ptr = rhs.m_ptr->clone(); }

    myfunction& operator=(const myfunction& rhs)
    {
        if (this == &rhs)
        {
            return *this;
        }

        delete m_ptr;
        m_ptr = rhs.m_ptr ? rhs.m_ptr->clone() : nullptr;
        return *this;
    }

    ~myfunction()
    {
        if (m_ptr)
        {
            delete m_ptr;
        }
    }
    myfunction(myfunction&& rhs)
    {
        if (m_ptr )
        {
            delete m_ptr;
        }
        m_ptr = rhs.m_ptr;
        rhs.m_ptr = nullptr;
    }

    Ret operator()(Args... args) { return m_ptr->invoke(std::forward<Args>(args)...); }

private:
    //辅助类基类,使用继承方式, 擦除掉构造函数的模板参数;这样可以在此类中定义 ICallAble 指针
    struct ICallAble
    {
        virtual Ret invoke(Args&&... args) = 0;
        virtual ICallAble* clone() = 0;
        virtual ~ICallAble() = default;
    };

    //包装一般可调用对象, 普通函数、lambda表达式、类静态函数、重载了()的类对象
    template <typename Function>
    struct CallAble : public ICallAble
    {
        CallAble(Function&& func) : m_real_func(std::forward<Function>(func)) {}
        ICallAble* clone() override { return new CallAble<Function>(std::forward<Function>(m_real_func)); }
        Ret invoke(Args&&... args) override
        {
            return m_real_func(std::forward<Args>(args)...);
        }
        ~CallAble() = default;

        Function m_real_func = nullptr;    //真正执行的函数
    };

    //包装类成员函数,类成员函数需要类对象才能进行调用,所以比普通版本多一个Class类型模板参数
    template<typename Function, typename Class>
    struct MemCallAble : public ICallAble
    {
        MemCallAble(Function&& func, Class* obj) : m_real_func(std::forward<Function>(func)), m_obj(obj)
        {

        }
        ICallAble* clone() override { return new MemCallAble<Function, Class>(std::forward<Function>(m_real_func), m_obj); }
        Ret invoke(Args&&... args) override
        {
            return (m_obj->*m_real_func)(std::forward<Args>(args)...);
        }
        ~MemCallAble() = default;

        Function m_real_func = nullptr; //真正执行的函数
        Class* m_obj = nullptr;         //类对象
    };

private:
    ICallAble* m_ptr = nullptr;
};

http://www.kler.cn/a/521129.html

相关文章:

  • 《程序人生》工作2年感悟
  • CE11.【C++ Cont】练习题组12(结构体专题)
  • Java 大视界 -- Java 大数据与碳中和:能源数据管理与碳排放分析(66)
  • node.js 07.npm下包慢的问题与nrm的使用
  • 寒假学web--day10
  • FFmpeg 头文件完美翻译之 libavcodec 模块
  • 算法日记9:SC61滑动窗口(单调队列)
  • c++学习第十二天
  • 神经网络|(五)概率论基础知识-条件概率
  • 开源AI模型发布策略:平衡开放与质量的艺术
  • 香港维尔利健康科技集团重金投资,内地多地体验中心同步启动
  • 第 434 场周赛解题(超详细)
  • 动态规划复习总结2
  • 数据结构初阶之队列的介绍与队列的实现
  • 嵌入式学习笔记-杂七杂八
  • Qt调用FFmpeg库实时播放UDP组播视频流
  • 51单片机入门_02_C语言基础0102
  • iOS开发 SDWebImage加载webp动图以及加载大量动图
  • USB 3.1 Legacy Cable and Connector笔记
  • World of Warcraft [CLASSIC] Jewelcrafting Gemstone 2
  • Java中的依赖注入(可以不使用@Autowired注解)
  • 蓝桥杯之c++入门(一)【数据类型】
  • 信息系统管理工程师第6-8章精讲视频及配套千题通关双双发布,附第14章思维导图
  • 哈希表的使用
  • 使用PyTorch实现逻辑回归:从训练到模型保存与加载
  • MySQL 8 不开通 CLONE 插件,建立主从关系