不带 __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,从中派生所有其他类型,如ManCar 等。

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]-&gt;speed()(您还必须在 Base 类中提供一个实现,或者使其成为具有纯虚拟的抽象类)。要调用特定于某个类的函数,应首先转换 Base 指针。一般来说,你不会提前知道它指向什么,所以dynamic_cast 是有序的。

以上是关于不带 __VA_ARGS__ 的可变参数宏的主要内容,如果未能解决你的问题,请参考以下文章

找出可变参数宏中__VA_ARGS__的类型

__VA_ARGS__

glibc 中确定宏参数个数的宏__SYSCALL_NARGS 及 可变参数宏__VA_ARGS__

如何在文件系统中找到可变参数宏实现

C++ 预处理器 __VA_ARGS__ 参数数量

可变参数宏中的参数计数无效