怪异的宏观扩张

时间:2017-05-02 12:47:47

标签: c macros

我想在c中模拟类,并用宏隐藏实现,但我得到了宏的意外扩展行为。

CREATE TABLE #test
(
    Id INT NOT NULL,
    LastName VARCHAR(MAX) NOT NULL
)

BULK INSERT #test
FROM 'C:\test.txt'
WITH
(
    MAXERRORS = 0,
    FIRSTROW = 1,
    FIELDTERMINATOR = ',',
    ROWTERMINATOR = '\r\n'
)

SELECT *
FROM #test 

编译器说我声明了两次struct classMethods(类应该是类的名称)。这意味着当我想要它时,“类”不会被替换。甚至可以这样做吗?

1 个答案:

答案 0 :(得分:2)

你的第一个问题是

#define end_methods } ## ;

是语法错误(如果扩展了宏),因为令牌粘贴的结果不是单个有效令牌。您应该收到错误消息,如

error: pasting "}" and ";" does not give a valid preprocessing token

您的第二个问题是在嵌套宏扩展之前执行了令牌粘贴。这意味着你的宏

#define decl_methods struct class ## Methods {

实际上和你写的一样

#define decl_methods struct classMethods {

为了让它做你想做的事,class必须是类似函数的宏的形式参数:

#define decl_class(class)   struct class {
#define end_class(class)    }; typedef struct class class;
#define decl_methods(class) struct class ## Methods {
#define end_methods(class)  };
#define method(class, returnType, methodName, ...) \
    returnType (*methodName)(struct class *self, __VA_ARGS__);

然后

decl_class(Double)
    double value;
    decl_methods(Double)
        method(Double, double, get_value);
    end_methods(Double)
end_class(Double)

我想你可以避免在每个宏调用中重复这个类的名称,方法是让一组额外的宏在那里粘贴class伪参数,但是(由于太多繁琐而无法进入的原因)在这里;非常仔细地阅读Argument Prescan 的" GNU CPP manual"部分,您将需要两个层嵌套扩展获得你想要的效果:

#define decl_class__(class_)   struct class_ {
#define decl_class_(class_)    decl_class__(class_)
#define decl_class             decl_class_(class)

#define decl_methods__(class_) struct class_ ## Methods {
#define decl_methods_(class_)  decl_methods__(class_)
#define decl_methods           decl_methods_(class)

/* etc */

从技术上讲,只有在最里面的宏需要使用##(或#)时才需要这样做,但如果您真的想在真实程序中使用这些宏,那么您应该这样做对所有人都是统一的,否则你会在六个月后撕掉你的头发。

在您通过所有 之后,您会发现您的method宏不适用于零参数方法,例如

#define class Integer
method(int, getValue)

要么抛出错误,因为在标准C中,宏参数列表中的...必须至少接收一个参数,否则它会扩展为语法无效的声明,

int (*getValue)(struct Integer *self, );

解决这个问题的唯一方法是使用GNU扩展:

#define method__(class_, returnType, methodName, ...) \
    returnType (*methodName)(struct class_ *self, ##__VA_ARGS__);

在GNU扩展C中,##,之间的__VA_ARGS__具有导致在...未收到参数时删除逗号的特殊效果。 (这个扩展大约15年前被提议用于标准化,但委员会并不感兴趣。)

此时我邀请您重新考虑仅使用C ++的可能性。