跟我学c++高级篇——静态反射实现之二函数接口实现

一、函数反射

在实际的编程中,类和结构体应用最多,但也最难。这里先分析函数反射,类和结构体放到后面在分析。函数是什么?其实在PC看来就是一个地址,在编译顺看来就是一个符号(废话啊)。函数反射的应用也非常多,比如通过一个字符串来得到相关的API调用。这个在一些动态调用中,非常有用。

举一个简单例子,一般C/C++开发者都使用过函数指针,而函数指针就可以实现一些和函数反射有点类似的功能。一般函数指针在应用时,都是通过值来判断是什么来决定调用哪个函数指针,这些值其实就可以是字符串类型,这就和反射很像了。但函数指针的实现有点小问题在于,一个函数指针,其特征(名称、参数个数、参数类型)基本就定了下来。这就不如反射灵活了。

有的开发者可能说,可以使用变参、变参模板啊。非常棒。

二、实现方式

先实现一个初级版本,通过std:function来实现一个映射版本:

c 复制代码
#include <iostream>
#include <unordered_map>
#include <functional>
#include <string>

void getData(int t)
{
    std::cout << "call getData function,pars is:"<<t << std::endl;
}
void getContent(int t)
{
    std::cout << "call getContent function,pars is:"<<t << std::endl;
}

std::unordered_map<std::string, std::function<void(int)>> umap;
void initMap()
{
    umap.emplace("getData",getData);
    umap.emplace("getContent",getContent);
}

int main()
{
    initMap();
    int d = 100;
    if (umap.count("getData"))
    {
        auto func = umap["getData"];
        func(d);
    }
    std::cout << "end" << std::endl;
}

代码很简单,但也很容易看明白。可前面提到过了,这种方法局限性还是有的,无法实现不同参数和参数类型的函数。这里有一种取巧的方法,可以用一个包含std::any的容器std::vector来组织一下,但这个就有一个问题,处理起来还是不方便。网上还有使用json字符串的,这个说法更麻烦了。如果本身反射就带着json处理还好,否则写个简单应用还需要带个json库,可就麻烦了。

三、利用模板万能函数

在前面分析过万能函数,可以在这个基础上实现一个动态处理函数反射的类:

c 复制代码
#pragma once
#include <string>
#include <unordered_map>


template <class T, class R, typename... Args>
class  CppDelegate
{
    R(T::* func_)(Args...);//万能函数
    typedef  decltype(func_) FUNC;
    //using FuncGloabl = R *(*)(Args...);

public:
    CppDelegate() {}

    void AddFunction(T *t,const std::string & funcname, FUNC func )
    {
        umap_.emplace(funcname,func);
        umap1_.emplace(funcname,t);

    }
    template<typename ...Args>
    R StartFunc(const std::string& funcname,Args...args)
    {
        auto type = this->getClass(funcname);
        auto func = this->getFunc(funcname);

        if (type != nullptr && func != nullptr)
        {
            return (type->*func)(std::forward<Args>(args) ...);
        }
        return R();
    }
private:
    FUNC getFunc(const std::string &funcname)
    {
        if (umap_.count(funcname) > 0)
        {
            return umap_[funcname];
        }


        return nullptr;
    }

    T* getClass(const std::string& name)
    {
        if (umap1_.count(name) > 0)
        {
            return umap1_[name];
        }

        return nullptr;
    }

private:
    std::unordered_map<std::string, FUNC> umap_;
    std::unordered_map<std::string, T*> umap1_;
};
class Data 
{
public:
    Data() {}
    ~Data() = default;
public:
    int GetData(int a) { std::cout << "call getData function,a value:"<< a<< std::endl; return 0; };
    int GetContent(int a, int b) { std::cout << "call getContent function:" << std::endl; return 0; };
};
Data* d = new Data;
void testReflect()
{
    CppDelegate<Data, int,int> cpp;
    cpp.AddFunction(d,"getData",&Data::GetData);
    auto f = cpp.StartFunc("getData",100);

    std::cout << "f is:" << std::endl;
}
void testVoid() {
    return void();
}
int main()
{
    testVoid();//这个在VS中没有问题
    testReflect();
    return 0;
}

其实如果只是适配静态和全局函数,这个就非常简单了,这里需要适配类成员函数,所以比较麻烦。上面的代码还有几个问题:

1、不同类的不同函数如何存储在一个容器中

2、return R()如何处理void 等特殊情况

3、如何保证t*的生命周期

解决其来也有办法,只是怎么看更优雅一些。第一个可以在调用类上再抽象一层;第二个可以用概念或者SFINAE控制;第三个就比较麻烦了,不过,目前这样做也可以保证基本使用。

四、总结

不断的抽象实现可以保证设计上的依赖于抽象而不依赖于实现,也就使得代码更有普适性。但多层次的抽象导致的结果可能是代码阅读上的困难和维护上不方便。这个就是仁者见仁了,一般来说,对于库等升级比较正式而且不怎么频繁的项目可以尽量抽象,而对于应用层,抽象要适当。

不过在现在的环境下,就根据情况自己选择吧。

相关推荐
CodeWithMe4 分钟前
【C/C++】std::vector成员函数清单
开发语言·c++
uyeonashi4 分钟前
【QT控件】输入类控件详解
开发语言·c++·qt
zh_xuan5 小时前
c++ 单例模式
开发语言·c++·单例模式
利刃大大7 小时前
【在线五子棋对战】二、websocket && 服务器搭建
服务器·c++·websocket·网络协议·项目
喜欢吃燃面7 小时前
C++刷题:日期模拟(1)
c++·学习·算法
SHERlocked937 小时前
CPP 从 0 到 1 完成一个支持 future/promise 的 Windows 异步串口通信库
c++·算法·promise
虚拟之8 小时前
36、stringstream
c++
我很好我还能学8 小时前
【面试篇 9】c++生成可执行文件的四个步骤、悬挂指针、define和const区别、c++定义和声明、将引用作为返回值的好处、类的四个缺省函数
开发语言·c++
南岩亦凛汀9 小时前
在Linux下使用wxWidgets进行跨平台GUI开发
c++·跨平台·gui·开源框架·工程实战教程
曦月逸霜10 小时前
第34次CCF-CSP认证真题解析(目标300分做法)
数据结构·c++·算法