添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

问题的起因是,我在做一个demo,有一个对象基类,以及一堆派生出的子对象,比如球体、立方体之类的对象。还有一个对象管理类,用于存储场景中的所有对象。那么在初始化的时候,代码是这么写的:

class ObjectInfo
private:
	vector<Object*> vecObjs;
public:
	void Init()
		vector<string> name =
			"Sphere",
			"Cube",
			"Cube",
			"Cone",
		for (int i = 0; i < name.size(); i++)
			Object* obj = nullptr;
			if (name[i] == "Sphere")
				obj = new Sphere();
			else if (name[i] == "Cube")
				obj = new Cube();
			else if (name[i] == "Cone")
				obj = new Cone();
			if (obj)
				vecObjs.push_back(obj);
			// ....

        虽然是不会有什么问题,但感觉这么写真的很蠢。所以开始研究自动做这件事的方法,也就是说,只要调一次Create()即可,不需要像上面这样写一长串的if / else,否则代码会比较难维护。

        现在,问题可以简化为,给定一个字符串,自动构造出对应的对象。

        C++目前已经支持了RTTI技术,在已有一个类的实例的时候,可以获取类的名字(字符串形式的)。但这点似乎并不能派上用场,因为我们需要在得到实例之前去做从字符串到类的解析。据我所知,大部分C++框架都会定义很多宏,来完成代码生成的过程,所以在使用这些框架的时候,每次定义一个类,都要加一堆奇奇怪怪的大写字符。我用过的包含MFC,Qt和Unreal,但具体实现细节,我还没有深究过。

        依照代码生成的思路,我以一个C++弱鸡的角度,思考如果我自己去做这么一个自动生成器,应该怎么实现。(也就是我的做法大概率不是非常好的,如果希望实际使用的话,最好借鉴一些成熟的解决方案

        首先,要给每个类定义一个函数,比如对于类Sphere,应该有这么一个Create函数(当然,我们所有的讨论是基于类有继承关系,以下例子中类Sphere继承自Object):

Object* Create()
    return new Sphere();

        这个函数也就是某个自动生成器最终调用的一个函数。首先它应该不能是类A成员函数,因为此时类A还没有实例化,是访问不到的。

        为了根据字符串找到这个函数,我想到的是,可以存在一个字符串到函数的map里,它大概长这么个样子:

map<string, function<Object*()>>

        也就是说,我们在定义一个类后,还需要完成如下的操作:

        (1) 生成一个对应的 Create()函数。

        (2) 把这个函数加入到map里。

        如果让宏来完成,那么Create函数的生成就是这样的:

#define CREATE(class_name) \
	Object* Create() { return new class_name();};\

        根据以上思路,又引入了一个单例的Helper类(可以理解为Factory)来封装一些东西,最终第一版是这样的:

#include <map>
#include <string>
#include <vector>
#include <iostream>
#include <functional>
using namespace std;
class Object;
#define REGISTER(class_name) \
	Helper::Inst()->Push(#class_name,[]()->Object* \
	{ return new class_name;});\
class Helper
private:
	map<string, function<Object*()>> mapStr2Func;
	static Helper* helper;
	Helper() {}
public:
	static Helper* Inst()
		if (!helper)
			helper = new Helper();
		return helper;
	Object* CreateObject(string name)
		if (mapStr2Func.find(name) != mapStr2Func.end())
			return mapStr2Func[name]();
		return nullptr;
	void Push(string name, function<Object*()> func) { mapStr2Func[name] = func; }
class Object
public:
	virtual void Print() = 0;
	const char* GetClassName()
		return typeid(*this).name();
class Sphere : public Object
	void Print() override { cout << GetClassName() << endl; }
class Cube : public Object
	void Print() override { cout << GetClassName() << endl; }
class Cone : public Object
	void Print() override { cout << GetClassName() << endl; }
class ObjectInfo
private:
	vector<Object*> vecObjs;
public:
	void Init()
		vector<string> name =
			"Sphere",
			"Cube",
			"Cube",
			"Cone",
		for (int i = 0; i < name.size(); i++)
			Object* obj = Helper::Inst()->CreateObject(name[i]);
			if (obj)
				vecObjs.push_back(obj);
				obj->Print();
Helper* Helper::helper = nullptr;
int main()
	REGISTER(Sphere)
	REGISTER(Cube)
	REGISTER(Cone)
	ObjectInfo obj;
	obj.Init();
	system("pause");

        以上代码最终打印的结果为:

       但这个代码有一处我非常不满意的地方,这个REGISTER宏只能放在函数体里,比如这里的main里,而我希望的是能够放在类声明的旁边。因为自动生成的代码包含了把函数放入map的过程,而这样的操作在全局空间中是不允许的,我们最多只能在全局空间写一些变量的声明加初始化,比如:

int x = 0;

       但无法做:

int x ;
x = 0; // forbidden!

       纠结这个宏所在位置的原因在于:类的声明和宏放在一起易于维护。

       就我个人经验而言,如果项目中有这么一个类Base,我想继承它做一个新的功能类,那么我一定会先参照它已有的另一个子类的代码,看它是如何写的,或者更直接的,我会把它复制过来,把不必要的东西删掉,留下必要的。如果宏和类的声明放在一起,那么这个东西我是不会落下的,照葫芦画瓢改一遍都不会出错。但作为新手而言,我肯定很难想到,我要到另外一个看起来毫不相干的类里,添加一句宏。

       作为改进,为了让以上操作(map的insert操作),能顺利在全局空间执行,我又构造了一个类Generator,利用它的构造过程偷偷完成了这一过程,也就是我可以在全局空间写:

Generator* generator = new Generator();

        然后把那一堆逻辑放在Generator的构造函数里。反正以上行为就是穿了个马甲。

        在头文件中定义变量其实是不符合规范的,为了稳妥可以把这个宏挪到对应的实现文件里,但出于个人强迫症,我希望只在头文件声明就足以。我用的vs 2017竟然可以编译过,但不确定其它编译器是否可行。(难道这是msvc编译器的特性?)

        最终的代码如下:

reflect.h

#pragma once
class Object;
#include <map>
using namespace std;
#define REGISTER(class_name) \
	 class class_name##Generator : public Generator{\
	 public:class_name##Generator() { \
		Helper::Inst()->Push(#class_name, this);}\
		Object* Create() {return new class_name();}};\
	  class_name##Generator* class_name##Inst = new class_name##Generator();
class Generator
public:
	virtual Object* Create() = 0;
class Helper
private:
	map<string, Generator*> mapStr2Generator;
	static Helper* helper;
	Helper() {}
public:
	static Helper* Inst()
		if (!helper)
			helper = new Helper();
		return helper;
	Object* CreateObject(string name)
		if (mapStr2Generator.find(name) != mapStr2Generator.end())
			return mapStr2Generator[name]->Create();
		return nullptr;
	void Push(string name, Generator* generator) { mapStr2Generator[name] = generator; }

test.h

#pragma once
#include <string>
#include <vector>
#include <iostream>
#include <functional>
#include "reflect.h"
class Object
public:
	virtual void Print() = 0;
	const char* GetClassName()
		return typeid(*this).name();
class Sphere : public Object
	void Print() override { cout << GetClassName() << endl; }
REGISTER(Sphere)
class Cube : public Object
	void Print() override { cout << GetClassName() << endl; }
REGISTER(Cube)
class Cone : public Object
	void Print() override { cout << GetClassName() << endl; }
REGISTER(Cone)
class ObjectInfo
private:
	vector<Object*> vecObjs;
public:
	void Init()
		vector<string> name =
			"Sphere",
			"Cube",
			"Cube",
			"Cone",
		for (int i = 0; i < name.size(); i++)
			Object* obj = Helper::Inst()->CreateObject(name[i]);
			if (obj)
				vecObjs.push_back(obj);
				obj->Print();

main.cpp

#include "test.h"
Helper* Helper::helper = nullptr;
int main()
	ObjectInfo obj;
	obj.Init();
	system("pause");
                        问题的起因是,我在做一个demo,有一个对象基类,以及一堆派生出的子对象,比如球体、立方体之类的对象。还有一个对象管理类,用于存储场景中的所有对象。那么在初始化的时候,代码是这么写的:class ObjectInfo{private:	vector&amp;lt;Object*&amp;gt; vecObjs;public:	void Init()	{		vector&amp;lt;strin...
				
最近项目需求中需要一个关键功能——根据字符串创建对象。由于C++没有似Java、C#这动态语言中的反射机制,所以在C++程序中一般用if…else…或者switch来将字符串或者枚举值与对象的创建方法来进行对应的映射。这里如果我们实现一个简单的反射机制,可以根据字符串创建对象就可以简化这个过程,而且无论对修改还是扩展都会更加方便。 理想的使用方式就是我们需要这个功能的统一继...
实现一个自己的String是一道考验C++基础知识的好题。能够准确无误地编写出String构造函数、拷贝构造函数、赋值函数和析构函数的面试者至少已经具备了C++基本功的60%以上! 在这个中包括了指针成员变量m_pData,当中包括指针成员变量时,一定要重载其拷贝构造函数、赋值函数和析构函数,这既是对C++程序员的基本要求,也是《Effective C++》中特别强调的条款。 之前一直想通过配置文件如xml,配置一个名,通过这个创建对象,通过基指针来指向派生,管理一系列这样对象,继承,这样的可以不修改管理咧,直接添加或修改配置文件来动态创建管理,非常方便。由于C++没有反射机制,但是可以实现。 (1)为需要反射的中定义一个创建对象的一个回调函数; (2)设计一个工厂中有一个std::map,用于保存名和创建...
Create C++ Object Dynamically Introduction C++不像C#和Java那样具有反射的能力,通常不能根据任意一个class name来创建该class的instance。但我们知道在MFC中,任何继承了CObject的都可以根据其名字来创建实例,它是使用了一些宏。而我从来就不喜欢使用大把的宏,虽然有的时候宏可能比较方便,可能对某些人来说也更美观。 最近在写一个SDK,想实现一个管理,通过传入不同的字符串,来实现动态创建不同。 在Java等一些语音中,实现了一个机制:反射,个人理解可以通过字符串创建对象,比如: std::string a = "testBase"; //反射即可以通过字符串变量‘a’,来达到创建”testBase“的目的 这里折中了一下,不通过传入名来达到创建对象,而是通过回调函数的名字来达到创建的目的。 C++实现 typedef void* (*funcp)(); 定义一个基,所有的动态
— 从 C 到 C++ 的进化过程中引入了自定义型, — 在 C++ 中可以通过完成字符串型的定义。 问题:在 C++ 中的原生型系统是否包含字符串型呢? XiebsLib 库中字符串的设计,结构如下图所示 方法一:使用parseInt()函数 parseInt()函数是JavaScript的内置函数,可以将字符串转换为整数。例如,假设有一个字符串变量str,存储了字符串"123",要将其转换为数字,可以使用如下代码: ```javascript var str = "123"; var num = parseInt(str); 在上面的代码中,parseInt()函数将字符串"123"转换为对应的整数123,并将其赋值给变量num。 方法二:使用parseFloat()函数 如果字符串表示的是一个浮点数,可以使用parseFloat()函数将其转换为浮点数。例如,假设有一个字符串变量str,存储了字符串"3.14",要将其转换为数字,可以使用如下代码: ```javascript var str = "3.14"; var num = parseFloat(str); 在上面的代码中,parseFloat()函数将字符串"3.14"转换为对应的浮点数3.14,并将其赋值给变量num。 需要注意的是,根据实际需求选择合适的方法进行转换。如果字符串中包含非数字字符,转换结果可能会出现异常。在使用时应谨慎处理异常情况,例如,可以使用isNaN()函数或正则表达式验证字符串是否为有效数字。另外,还可以使用Number()构造函数等其他方法进行字符串转数字操作。