不带 __VA_ARGS__ 的可变参数宏
Posted
技术标签:
【中文标题】不带 __VA_ARGS__ 的可变参数宏【英文标题】:Variadic Macro without __VA_ARGS__ 【发布时间】:2014-03-03 16:17:28 【问题描述】:所以,这基本上就是我想要做的:
#define RS03(obj, a1, a2, a3) if (_str1 == #a1) _file >> _##a1; if (_str1 == #a2) _file >> _##a2;if (_str1 == #a3) _file >> _##a3; obj (_##a1, _##a2, _##a3);
这是三个参数的情况,但我还需要:
#define RS04(obj, a1, a2, a3, a4)
#define RS05(obj, a1, a2, a3, a4, a5)
#define RS06(obj, a1, a2, a3, a4, a5, a6)
...
所以是可变参数宏。
*** 上有很多关于此类主题的问题,但不适用于我的案例。
在上面的代码中,三个参数 a1、a2 和 a3 既用作字符串(在“if”条件中)也用作变量(在赋值和构造函数中),而 obj 是一个类(所以宏中的最后一个命令是构造函数调用)。
重点是:假设我有 20 个不同的类,每个类都需要不同数量的输入来构造。
宏接收类的名称和构建此类对象所需的参数名称。
重点是(参见下面的“设计原因”)我需要在“if”条件下使用参数的名称,就像字符串一样。这就是为什么我使用宏(具有 # 和 ## 特殊字符的优点)而不是模板函数的原因。
基本上,我需要一个纯文本转换(所以是一个宏,而不是一个函数),但需要一个变量名的参数。
这种设计的原因
假设我有 20 个不同的类,每个类都非常不同(例如,类 1 可以用两个 double 构造,类 2 需要一个 double 和两个整数,类 3 可以用两个构造布尔等...)。
所有这些类都有一个“运行”成员函数,它产生相同格式的输出。
我的程序应该执行以下操作:
1 - 读取文本文件
2 - 为文件中描述的每个模型启动运行
文件应该是这样的:
car
name = model1
speed = 0.05
man
name = model2
speed = 0.03
male = true
ageclass = 3
...
所以我需要读取这样的文件并初始化文件中描述的每个类。
参数应该按照用户喜欢的顺序编写。
另外,它们可能出现不止一次,例如:
car
name = pippo
speed = 0.05
speed = 0.06
speed = 0.07
(在这种情况下,最后一个会覆盖另一个)
如果用户忘记了某些参数,程序应该停止并显示明确的错误消息。
同一类型可能有不同的模型(例如,4个不同的平面模型)。
例如,在这种情况下:
car
name = pippo
speed = 0.05
car
name = pluto
程序应该说第二个模型不完整。
当然有很多方法可以做到这一点。
我是这样做的:
1 - 创建一个模板类(我们称之为“字段”),其中包含一个 T 成员(存储值)和一个 bool 成员(告诉我变量是否存在)
2 - 创建一个具有多个“字段”的对象(读取器,读取文件的对象),每个可能的模型属性(_name、_speed、_male 等)一个。
3 - 然后,在读取文件时,对于括号内的部分,我首先读取一个字符串(“标签”),然后读取“=”,最后读取值。该值将存储在与字符串同名的变量中
(如果我找到“速度 = 0.03”这一行,我将在阅读器的字段_name 中保存 0.03)。
这应该是伪代码(可能有错误,只是为了说明):
if (car)
while (str != "")
if (str == "speed") _file >> _speed; _file >> _str;
if (str == "male") _file >> _male; _file >> _str;
if (str == "ageclass") _file >> _ageclass; _file >> _str;
ERROR;
car (_speed.get (), _male.get (), _ageclass.get ());
get() 是“字段”类的成员函数,如果不可用(即文件中不存在)则引发异常,否则返回值(从文件中读取)。
类“字段”还有一个重载的运算符>>,它将标准运算符>>应用于值,并将布尔属性设置为true。
由于模型太多,我想在宏中转换代码:-)
【问题讨论】:
Boost.Preprocessor 有这方面的工具。 不能使用(可变参数)模板解决这个问题吗?在处理 C++ 时,它们应该总是优先于宏。 @JorenHeit 不是真的(在这种情况下),请注意预处理器标记连接运算符的使用。 @Angew 所有这些宏都可能是糟糕(即不是纯 C++)设计的标志 ;-) 我将宏用作文本转换:我必须重复相同的代码大约二十次,但稍作修改。我想在一个位置编写代码,这样我就可以在一个位置进行编辑。我使用宏而不是函数是因为我在主题末尾已经解释过(我需要以两种不同的形式使用相同的变量) 【参考方案1】:我不确定您是否可以像我即将提议的那样彻底更改您的实施,但这是我会做的。它不需要任何奇异的模板技术。
首先,您创建一个名为Properies
(例如)的结构,其中包含任何类可以禁止的所有属性。
struct Properties
enum Types
MAN,
CAR,
// and more
;
enum Gender
MALE, FEMALE
;
Types type;
string name;
double speed;
Gender gender;
int ageClass;
;
如您所见,它还包含一个enum
,用于描述每个现有类型。
接下来,您定义一个Base
-type,从中派生所有其他类型,如Man
、Car
等。
class Base
;
class Man: public Base
string d_name;
Properties::Gender d_gender;
int d_ageClass;
double speed;
public:
Man(Properties const &properties)
// Set properties that apply to the "Man"-class
;
class Car: public Base
string d_name;
double d_speed;
public:
Car(Properties const &properties)
// Set properties that apply to the "Car"-class
;
每个类的构造函数都需要一个Properties
对象,从中提取适合它们的字段。例如,Car
构造函数不会检查 gender
字段,而 Man
构造函数会。
现在,您定义一个 Loader
类来处理解析。它包含一个成员readFile
,它返回一个Base*
的向量,这样您就可以在一个容器中拥有指向所有已初始化对象的指针。我选择shared_ptr
来处理所有权问题,但您应该决定什么最适合您的应用程序。
class Loader
public:
static vector<shared_ptr<Base>> readFile(string const &fileName)
vector< shared_ptr<Base> > result;
ifstream file(fileName);
// Parse the file, creating a "Properties" object, called
// "props" here
while (file) // while EOF not reached.
Properties props = parse(file); // implement your parse
// routine, returning Properties.
switch (props.type)
case Properties::CAR:
result.push_back(shared_ptr<Base>(new Car(props)));
break;
case Properties::MAN:
result.push_back(shared_ptr<Base>(new Man(props)));
break;
// etc for all classes derived from Base
default:
throw string("error: unknown type");
;
希望这会有所帮助。
【讨论】:
好的。这不是那么激烈。事实上,我认为我根本不需要构建任何新课程。基本上,如果我错了,请纠正我,您建议我阅读所有可能的字段(在“解析”函数中,我需要与所有可能对象的所有可能字段一样多的“if”条件)并将它们全部传递给对象,而这些对象又将只使用它们真正需要的字段。 @RichterBernadell 没错。类的共同功能当然应该是虚拟的,例如,为了做到vec[i]->speed()
(您还必须在 Base 类中提供一个实现,或者使其成为具有纯虚拟的抽象类)。要调用特定于某个类的函数,应首先转换 Base 指针。一般来说,你不会提前知道它指向什么,所以dynamic_cast
是有序的。以上是关于不带 __VA_ARGS__ 的可变参数宏的主要内容,如果未能解决你的问题,请参考以下文章