可以运行时绑定编译时信息?

Enter空格 2016-12-14 03:06:16
加精
写了个依靠字符串名字调用函数的类regFunc 。
碰到个问题,浓缩问题的代码如下(需c++14)。

#include <iostream>
#include <unordered_map>
using namespace std;

void test() { printf("123\n"); }
void test(int i) { printf("123 i is %d\n",i); }

class regFunc {
unordered_map<string, void*> unmap_name_funcinfo;

public:
template<class T>
void insert(const string& funcName, T funcAddr) {
unmap_name_funcinfo.insert({ funcName ,(void*)funcAddr });
}

template<class T,class ...ARGS>
auto run(const string& funcName, ARGS... args){
auto funcObj = (T)unmap_name_funcinfo.find(funcName)->second;
return funcObj(args...);
}
};

int main() {
typedef void(*iterFunc1)();
typedef void(*iterFunc2)(int);

unordered_map<string, void*> map;

regFunc reg;
reg.insert("test", ((iterFunc1)test));
reg.insert("test@i", ((iterFunc2)test));

reg.run<iterFunc1>("test");
reg.run<iterFunc2>(string("test@i"),20);
}


我想实现的目标是调用reg.run的时候,不需填写类型信息,由注册时给出的信息自动找到对应的类型。
但这样做的话run就是单纯的运行时函数了,想了半天也没想出怎么把编译时信息存进来,
我的目标可行吗?或者根本无法实现?
...全文
3725 37 打赏 收藏 转发到动态 举报
写回复
用AI写文章
37 条回复
切换为时间正序
请发表友善的回复…
发表回复
ID870177103 2016-12-20
  • 打赏
  • 举报
回复
可以考虑一下入口函数为什么要特定的参数表 如果所有待调用函数参数一致,用GetProcAdress是最有效的
GKatHere 2016-12-20
  • 打赏
  • 举报
回复
如果使用运行时多态,不考虑效率,那么 1: 函数返回值都继承于同一个基类 2: 函数参数都为va_list, 函数中求参数, 那么就很简单了 编译时?再想想


class RT{	};
class LRT	:public RT
{	public: long v;	};
class STRINGRT	:public RT
{	public: std::string v;	};


LRT* r1(va_list ap)
{
	auto a =new LRT;
	a->v =1;
	return a;
}

//STRINGRT* r2(LPCSTR s)
STRINGRT* r2(va_list ap)
{
	LPCSTR s =*(LPCSTR*)ap;
	auto a =new STRINGRT;
	a->v.assign(s);
	return a;
}

typedef RT* (*RTVSFUN)(va_list ap);
static const RTVSFUN FUS[] =
{
	(RTVSFUN)r1, (RTVSFUN)r2,
};

RT* Run(int idx, ...)
{
	va_list ap; 
	va_start(ap, idx); 

	RT* v = FUS[idx](ap);
	
	va_end(ap); 

	return v;
}

int _tmain(int argc, _TCHAR* argv[])
{
	LRT* r1Rt = static_cast<LRT*>(Run(0));
	STRINGRT* r2Rt = static_cast<STRINGRT*>(Run(1, "That's it!"));

	cout <<r1Rt->v <<endl;
	cout <<r2Rt->v.c_str()<<endl;
	
	delete r1Rt;
	delete r2Rt;
	
	printf("The End\n");
	::getchar();
	return 1;
}


fefe82 2016-12-20
  • 打赏
  • 举报
回复
MFC 的 MESSAGE_MAP 应该也可以参考下。 印象中里面应该是枚举了所有可以支持的函数类型。
qq_37084730 2016-12-19
  • 打赏
  • 举报
回复
同求同求!!!
lunat 2016-12-16
  • 打赏
  • 举报
回复
参考QT,MetaObject概念,就是通过所谓的元数据,记录程序本身的一些信息,在运行时使用。 C#/Java都有类似基于元数据的反射机制。
xiaocongzhi 2016-12-15
  • 打赏
  • 举报
回复
转入C++不久 进来观望
  • 打赏
  • 举报
回复
参考 std::function 的实现 C++设计新思维
Enter空格 2016-12-15
  • 打赏
  • 举报
回复
谢谢啦~paschen 粗略看了下,返回值貌似还是不能全自动的啊, 不过通过你给的代码已经给出思路了, 我在研究研究,这问题主要就是类型擦除与类型恢复。 这贴我过阵子再结,这代码思路还是很有用的,希望让更多的人看到。
clever101 2016-12-15
  • 打赏
  • 举报
回复
引用 11 楼 zhao4zhong1 的回复:
[quote=引用 6 楼 mymixing 的回复:] [quote=引用 5 楼 zhao4zhong1 的回复:] 模板是语法糖。 语法糖越甜,编译调试查错越苦! 把有限的生命浪费在品尝/品鉴无穷多种的语法糖中,我认为不值当。 模板技术比不上代码生成技术。我觉得。
赵老师,您还是只回答回答C的问题得了。 其他的高级语言,跨语言编程什么的,还是别回答了。 没什么帮助。 [/quote] 不要做A语言代码修改为B语言代码的无用功。 也不要做用A语言代码直接调用B语言代码库这样复杂、这样容易出错的傻事。 只需让A、B语言代码的输入输出重定向到文本文件,或修改A、B语言代码让其通过文本文件输入输出。 即可很方便地让A、B两种语言之间协调工作。 比如: A将请求数据写到文件a.txt,写完后改名为aa.txt B发现aa.txt存在时,读取其内容,调用相应功能,将结果写到文件b.txt,写完后删除aa.txt,改名为bb.txt A发现bb.txt存在时,读取其内容,读完后删除bb.txt 以上A可以替换为任何一种开发语言或开发环境,B可以替换为任何一种与A不同的开发语言或开发环境。 除非A或B不支持判断文件是否存在、文件读写和文件更名。 但是谁又能举出不支持判断文件是否存在、文件读写和文件更名的开发语言或开发环境呢? 可以将临时文件放在RamDisk上提高效率减少磨损磁盘。 数据的结构很复杂的话,文本文件的格式问题可参考json或xml 共享临时文本文件这种进程之间的通讯方法相比其它方法的优点有很多,下面仅列出我现在能想到的: ·进程之间松耦合 ·进程可在同一台机器上,也可跨机,跨操作系统,跨硬件平台,甚至跨国。 ·方便调试和监视,只需让第三方或人工查看该临时文本文件即可。 ·方便在线开关服务,只需删除或创建该临时文本文件即可。 ·方便实现分布式和负载均衡。 ·方便队列化提供服务,而且几乎不可能发生队列满的情况(除非硬盘空间满) ·…… “跨语言、跨机,跨操作系统,跨硬件平台,跨国,跨*.*的”苦海无边, 回头是“使用共享纯文本文件进行信息交流”的岸! 请牢记:源代码本身的书写是否结构化或面向对象或符合设计模式或敏捷…并不重要,重要的是你是否使用结构化或面向对象或符合设计模式或敏捷…的方法命名标识符、阅读、修改、检查、测试源代码。 意思是你程序结构看上去再合理,再简洁,也不一定比看上去一团乱麻的程序结构在运行或修改时更不易出错,更方便修改,出错了更容易找到哪里出错和具体出错的原因,更容易改正错误。 试对比 图书馆(对图书的分类够结构化了吧) 和 搜索引擎(可看作是扁平化任何结构数据,仅支持全文检索) 哪个处理信息更方便、更高效。 所以 与其费劲去重构代码让其看上去更简洁、更合理 不如费劲学习grep、sed、awk、……这类全文搜索和批处理编辑的工具。 结构越复杂,越难修改,越难除错。 有时(甚至大多数时候),看上去越合理、越简洁的代码,运行起来性能越差,出错时查找原因越难,找到出错原因后改正越费劲。 程序员要做的不是尽力避免错误,而是聚焦在快速发现并改正错误。真正以快速方式轻易解决错误,“快速的失败”远胜过“预防错误”。Fred George 前微软C#编辑器的开发主管Jay Bazuzi列出的一些有助于找到正确方向的问题;他觉得前同事们应该用这些问题来问自己;实际上不管在哪里工作的开发者们都应该经常问问自己这些问题: ◆“要保证这个问题不会再出现,我该怎么做?” ◆“要想少出些Bug,我该怎么做?” ◆“要保证Bug容易被修复,我该怎么做?” ◆“要保持对变化的快速响应,我该怎么做?” ◆“要保证我的软件的运行速度,我该怎么做?” 如果大多数团队都能不时问一下自己,必定会从中得益,因为这些都是真正强而有力的问题。 [/quote] 受教了,赵老师!
cattpon 2016-12-15
  • 打赏
  • 举报
回复
learning~
hugh_z 2016-12-15
  • 打赏
  • 举报
回复
666666666666666
Jermy Li 2016-12-15
  • 打赏
  • 举报
回复
也提供一种方式: 实现楼主的目标, 思想就是通过模板萃取保存函数的类型信息, 然后调用时根据函数名称与参数类型(参数检查)来匹配. 如果可以利用C++11的可变模板参数及std::forward()则实现会更加容易, 否则会复杂一些(需要自己手动完成, 可以不依赖c++11. 下面示例中用的是该方法). 其中有一个需要注意的地方, 如果调用时需要提供参数个数及类型信息, 那就会导致失去动态调用的最大好处. 比如我们已经知道需要调用函数"add"了, 那不如直接写add(1, 2)来代替call<int(int, int)>("add", 1, 2)或call<int>("add", 1, 2). 大部分需要场景应该是这样的: 不论调用什么函数, 什么样的参数个数及参数类型, 都可以以一种通用的方式来调用, 形如: call(funcName, args). 示例:

double add(int a, double b)
{
	return a + b;
}

double mul(int a, double b)
{
	return a * b;
}

void test()
{
	try{
		ObjectList args;
		args.add(toObject(12));
		args.add(toObject(6));

		Object* result = dispatcher.call("add", args);
		delete result;

		result = dispatcher.call("mul", args);
		delete result;

	}catch (Exception& e){
		e.printStackTrace();
	}
}
详细代码见: testBaseType(): https://github.com/javeme/brpc/blob/master/test.cpp#L299 testDispatcher(): https://github.com/javeme/brpc/blob/master/test.cpp#L537 这种动态调用(所谓反射)的典型使用场景是RPC, ORM(setter/getter)等. 更多关于C++实现RPC调用方法见: https://github.com/javeme/brpc
Enter空格 2016-12-15
  • 打赏
  • 举报
回复
引用 34 楼 heyuhang112 的回复:
这是个好贴啊!12楼正解也! 看了下其他回复,有人认为这是个没什么用的问题,其实不然,个人认为,这其实是个很实用的技巧。比如说在做服务器时,此技巧可以很方便的实现消息处理函数注册、消息派发。 我也分享一下我的实现吧,和其他方法原理都是一样,只不过实现不同,个人认为我的方法更加简便一些。

#include <stdio.h>
#include <assert.h>
#include <string>
#include <unordered_map>

using namespace std;

class Invoker
{
public:
	virtual ~Invoker() {}
};

template<typename R, typename... Args>
class InvokerT : public Invoker
{
public:
	typedef R(*FuncT)(Args...);

public:
	InvokerT(FuncT func) : _func(func) {}
	~InvokerT() {}

	R Run(Args&& ... args)
	{
		return _func(std::forward<Args>(args)...);
	}

private:
	FuncT _func;
};

class regFunc 
{
public:
	template<typename R, typename... Args>
	void insert(const std::string & name, R(*func)(Args...))
	{
		auto invoker = new InvokerT<R, typename std::decay<Args>::type...>(func);
		assert(invoker);
		_invokers[name] = invoker;
	}

	template<typename R, typename... Args>
	R run(const string & name, Args&& ... args)
	{
		auto it = _invokers.find(name);
		if (it == _invokers.end())
		{
			assert(false);
			return R();
		}

		auto real_invoker = dynamic_cast<InvokerT<R, typename std::decay<Args>::type...>*>(it->second);
		if (real_invoker == nullptr)
		{
			assert(false);
			return R();
		}

		return real_invoker->Run(std::forward<Args>(args)...);
	}

private:
	unordered_map<string, Invoker*> _invokers;
};

void test() 
{
	printf("123\n");
}

int test(int i)
{
	printf("123 i is %d\n", i); 
	return 0;
}

int main() 
{
	typedef void(*iterFunc1)();
	typedef int(*iterFunc2)(int);

	regFunc reg;
	reg.insert("test", ((iterFunc1)test));
	reg.insert("test@i", ((iterFunc2)test));

	reg.run<void>("test");
	reg.run<int>("test@i", 20);

	return 0;
}
模版+多态。 把函数map装载的函数对象包装成父类。 运行时利用多态调用run。 唉,脑子还是不够活跃,怎么就想不到这样用呢。
yes-hyh 2016-12-15
  • 打赏
  • 举报
回复
这是个好贴啊!12楼正解也! 看了下其他回复,有人认为这是个没什么用的问题,其实不然,个人认为,这其实是个很实用的技巧。比如说在做服务器时,此技巧可以很方便的实现消息处理函数注册、消息派发。 我也分享一下我的实现吧,和其他方法原理都是一样,只不过实现不同,个人认为我的方法更加简便一些。

#include <stdio.h>
#include <assert.h>
#include <string>
#include <unordered_map>

using namespace std;

class Invoker
{
public:
	virtual ~Invoker() {}
};

template<typename R, typename... Args>
class InvokerT : public Invoker
{
public:
	typedef R(*FuncT)(Args...);

public:
	InvokerT(FuncT func) : _func(func) {}
	~InvokerT() {}

	R Run(Args&& ... args)
	{
		return _func(std::forward<Args>(args)...);
	}

private:
	FuncT _func;
};

class regFunc 
{
public:
	template<typename R, typename... Args>
	void insert(const std::string & name, R(*func)(Args...))
	{
		auto invoker = new InvokerT<R, typename std::decay<Args>::type...>(func);
		assert(invoker);
		_invokers[name] = invoker;
	}

	template<typename R, typename... Args>
	R run(const string & name, Args&& ... args)
	{
		auto it = _invokers.find(name);
		if (it == _invokers.end())
		{
			assert(false);
			return R();
		}

		auto real_invoker = dynamic_cast<InvokerT<R, typename std::decay<Args>::type...>*>(it->second);
		if (real_invoker == nullptr)
		{
			assert(false);
			return R();
		}

		return real_invoker->Run(std::forward<Args>(args)...);
	}

private:
	unordered_map<string, Invoker*> _invokers;
};

void test() 
{
	printf("123\n");
}

int test(int i)
{
	printf("123 i is %d\n", i); 
	return 0;
}

int main() 
{
	typedef void(*iterFunc1)();
	typedef int(*iterFunc2)(int);

	regFunc reg;
	reg.insert("test", ((iterFunc1)test));
	reg.insert("test@i", ((iterFunc2)test));

	reg.run<void>("test");
	reg.run<int>("test@i", 20);

	return 0;
}
  • 打赏
  • 举报
回复
可以注册函数的时候把它的名字、参数类型、地址以某种方式持久化,不管叫什么,RTTI还是配置文件也好,总之运行的时候读信息再调用,难点在于如果注册和运行不是在同一程序的同一次运行期间,怎么修正函数地址是一个问题,如果能保证被调用的函数在DLL中就好办了。 实际上老赵说得有道理:模板技术比不上代码生成技术,这种需求如果能运行时动态生成代码就很容易实现。
kkgflsd 2016-12-15
  • 打赏
  • 举报
回复
支持一下下
ljheee 2016-12-14
  • 打赏
  • 举报
回复
不需填写类型信息,由注册时给出的信息自动找到对应的类型
paschen 版主 2016-12-14
  • 打赏
  • 举报
回复
引用 19 楼 ipqtjmqj 的回复:
[quote=引用 18 楼 paschen 的回复:] [quote=引用 17 楼 ipqtjmqj 的回复:] [quote=引用 16 楼 paschen 的回复:] [quote=引用 15 楼 ipqtjmqj 的回复:] iterFunc1 iterFunc2 是手动定义的,无法枚举出所有的类型。 说到底模板特化还是在编译时,要在运行时调用一个任意类型的函数对于c++还是做不到
iterFunc1、iterFunc2只为确定函数重载的版本,如果你的函数叫test1与test2,那么就不需要[/quote] 那你注册时传了iterFunc1, iterFunc2。[/quote]

void test1() { printf("123\n"); }

void test2(int i) { printf("123 i is %d\n", i); }

int main()
{
	typedef void(*iterFunc1)();

	regFunc reg;
	reg.insert("test1", &test1);
	reg.insert("test2",&test1);

	reg.run_void("test1");
	reg.run_void("test2", 20);

    return 0;
}

[/quote] 不是一样的吗,&test1,&test1只能在编译时确定,无法在运行时传入[/quote] 这个是,你要把运行时调用的函数先注册了,上面代码实际是放到一个map里
ipqtjmqj 2016-12-14
  • 打赏
  • 举报
回复
引用 18 楼 paschen 的回复:
[quote=引用 17 楼 ipqtjmqj 的回复:] [quote=引用 16 楼 paschen 的回复:] [quote=引用 15 楼 ipqtjmqj 的回复:] iterFunc1 iterFunc2 是手动定义的,无法枚举出所有的类型。 说到底模板特化还是在编译时,要在运行时调用一个任意类型的函数对于c++还是做不到
iterFunc1、iterFunc2只为确定函数重载的版本,如果你的函数叫test1与test2,那么就不需要[/quote] 那你注册时传了iterFunc1, iterFunc2。[/quote]

void test1() { printf("123\n"); }

void test2(int i) { printf("123 i is %d\n", i); }

int main()
{
	typedef void(*iterFunc1)();

	regFunc reg;
	reg.insert("test1", &test1);
	reg.insert("test2",&test1);

	reg.run_void("test1");
	reg.run_void("test2", 20);

    return 0;
}

[/quote] 不是一样的吗,&test1,&test1只能在编译时确定,无法在运行时传入
paschen 版主 2016-12-14
  • 打赏
  • 举报
回复
引用 17 楼 ipqtjmqj 的回复:
[quote=引用 16 楼 paschen 的回复:] [quote=引用 15 楼 ipqtjmqj 的回复:] iterFunc1 iterFunc2 是手动定义的,无法枚举出所有的类型。 说到底模板特化还是在编译时,要在运行时调用一个任意类型的函数对于c++还是做不到
iterFunc1、iterFunc2只为确定函数重载的版本,如果你的函数叫test1与test2,那么就不需要[/quote] 那你注册时传了iterFunc1, iterFunc2。[/quote]

void test1() { printf("123\n"); }

void test2(int i) { printf("123 i is %d\n", i); }

int main()
{
	typedef void(*iterFunc1)();

	regFunc reg;
	reg.insert("test1", &test1);
	reg.insert("test2", &test2);

	reg.run_void("test1");
	reg.run_void("test2", 20);

    return 0;
}

加载更多回复(17)

64,647

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧