【问题标题】:How do I replace this preprocessor macro with a #include?如何用#include 替换这个预处理器宏?
【发布时间】:2010-09-25 12:47:49
【问题描述】:

更新: 显然,您希望使用模板或基类而不是宏来执行此操作。不幸的是,由于各种原因,我不能使用模板或基类。


目前我正在使用宏在各种类上定义一堆字段和方法,如下所示:

class Example
{
  // Use FIELDS_AND_METHODS macro to define some methods and fields
  FIELDS_AND_METHODS(Example)
};

FIELDS_AND_METHODS 是一个使用字符串化和标记粘贴运算符的多行宏。

我想用下面的东西替换它

class Example
{
  // Include FieldsNMethods.h, with TYPE_NAME preprocessor symbol
  // defined, to achieve the same result as the macro.
  #define TYPE_NAME Example
  #include "FieldsNMethods.h"
};

这里我#define类的名称(以前是宏的参数),FieldsNMethods.h文件包含原始宏的内容。但是,因为我是#include,所以我可以在运行时、调试时单步执行代码。

但是,我在 FieldsNMethods.h 文件中“字符串化”和“标记粘贴”TYPE_NAME 预处理器符号时遇到问题。

例如,我想在FieldsNMethods.h中定义类的析构函数,所以这需要使用TYPE_NAME的值如下:

~TYPE_NAME()
{
  //...
}

但是用 TYPE_NAME 替换它的值。

我正在尝试的可能吗?我不能直接使用字符串化和标记粘贴运算符,因为我不在宏定义中。

【问题讨论】:

    标签: c++ macros include c-preprocessor


    【解决方案1】:

    这需要模板。

    class Example<class T>
    {
        ...class definition...
    };
    

    您问题最后一部分的直接答案 - “鉴于我不再处于宏定义中,我如何让粘贴和字符串化运算符工作” - 是“你不能”。这些运算符只能在宏中工作,因此您必须编写宏调用才能使它们工作。

    已添加

    @mackenir 说“模板不是一种选择”。为什么模板不是一个选项?该代码以老式的预标准、预模板方式模拟模板,这样做会造成很多痛苦和悲伤。使用模板可以避免这种痛苦——尽管会有转换操作。

    @mackenir 问道“有没有办法让宏使用?”是的,你可以,但你应该使用模板——它们更可靠和可维护。要使其与宏一起使用,您必须将包含的标头中代码中的函数名称作为宏调用。 您需要经过一定程度的间接才能使其正常工作:

    #define PASTE_NAME(x, y) PASTE_TOKENS(x, y)
    #define PASTE_TOKENS(x, y) x ## y
    
    #define TYPE_NAME Example
    int PASTE_NAME(TYPE_NAME, _function_suffix)(void) { ... }
    

    这种间接级别对于标记化和字符串化操作符来说通常是必要的习惯用法。


    来自@mackenir 的其他 cmets 表明问题仍然存在。让我们把它具体化。

    目前我正在使用宏在各种类上定义一堆字段和方法,如下所示:

    class Example
    {
        // Use FIELDS_AND_METHODS macro to define some methods and fields
        FIELDS_AND_METHODS(Example)
    };
    

    FIELDS_AND_METHODS 是一个使用字符串化和标记粘贴运算符的多行宏。

    我想用下面的东西替换它

    class Example
    {
        // Include FieldsNMethods.h, with TYPE_NAME preprocessor symbol
        // defined, to achieve the same result as the macro.
        #define TYPE_NAME Example
        #include "FieldsNMethods.h"
    };
    

    好的。为了具体化,我们需要一个FIELDS_AND_METHODS(type) 宏,它是多行的并使用标记粘贴(我不打算处理字符串化 - 但同样的基本机制将适用)。

    #define FIELDS_AND_METHODS(type) \
        type *next; \
        type() : next(0) { } \
        type * type ## _next() { return next; }
    

    幸运的是,这声明了“指向参数类型的指针”类型的成员、该类型的构造函数以及返回该指针的方法(在本例中为 Example_next)。

    所以,这可能是宏 - 我们需要替换它,以便 '#include' 完成相同的工作。

    fieldsNmethods.h的内容变成:

    #ifndef TYPE_NAME
    #error TYPE_NAME not defined
    #endif
    #define FNM_PASTE_NAME(x, y)    FNM_PASTE_TOKENS(x, y)
    #define FNM_PASTE_TOKENS(x, y)  x ## y
    
    TYPE_NAME *next;
    TYPE_NAME() : next(0) { }
    TYPE_NAME * FNM_PASTE_NAME(TYPE_NAME, _next)() { return next; }
    
    #undef FNM_PASTE_NAME
    #undef FNM_PASTE_TOKENS
    

    请注意,标头不会包含多重包含保护;它存在的理由是允许它被包含多次。它还取消定义了它的辅助宏以允许多次包含(好吧,因为重新定义是相同的,它们是“良性的”并且不会导致错误),我在它们前面加上 FNM_ 作为原始名称空间控制宏。这会生成我期望来自 C 预处理器的代码。并且 G++ 不会机智但会生成一个空的目标文件(因为我的示例代码中没有使用声明的类型)。

    请注意,除了问题中概述的代码外,这不需要对调用代码进行任何更改。我认为应该使用 SPOT“单点真理”原则(或干“不要重复自己”)来改进这个问题:

    #define TYPE_NAME Example
    class TYPE_NAME
    {
        // Include FieldsNMethods.h, with TYPE_NAME preprocessor symbol
        // defined, to achieve the same result as the macro.
        #include "FieldsNMethods.h"
    };
    

    【讨论】:

    • 谢谢。模板不是一种选择。那么,您是否暗示有一种方法可以达到与 FieldsNMethods.h 中的字符串化和标记粘贴运算符相同的效果?
    • 这些类是面向公众的 API,我们不想通过制作模板或拥有基类来污染它们。此外,它们是 C++/CLI 类,因此不能选择私有继承。感谢您提供更多信息,我会尝试一下。
    • '污染'我的意思是'以可能使 API 用户混淆的方式公开实现细节'。
    • 你用宏来做这个?听起来很邪恶!难道你不能写一个 if 门面来只向客户公开你想要的东西并且内部有可维护的东西吗?
    • 我们不想引入很多间接层级,而且我们仍然需要宏来传递到内部实现(否则必须多次编写大量相同的代码)。
    【解决方案2】:

    不,您不能即时定义类或函数定义。必须通过直接输入或在预处理器中定义它们来指定它们。

    通常,不需要生成这样的类,并且类定义是在编译之前创建的,无论是通过键入所有内容还是使用某种代码生成。有时会有单独的代码生成步骤(例如,在当前的 Visual Studio 中,您可以定义预处理和后处理步骤)。

    现在,如果您需要为不同的数据类型创建某些类的不同版本,您可以使用模板。您不能以这种方式创建不同名称的橡皮图章类。

    最后一个问题:你为什么要这样做?我从来没有遇到过这样的事情在 C++ 中看起来很有用,而在确实有意义的语言中,有一些工具可以做到这一点。

    【讨论】:

    • 我试图做的是有效地“参数化”#include,就像可以参数化宏一样。所以没有即时生成。这一切都在(实际上,之前)编译时间。
    【解决方案3】:

    您应该用另一个宏包装字符串化(由于预处理器的工作方式,需要 2 个)

    在 FieldsNMethods.h 中

    #define MAKE_STR_X( _v ) # _v
    #define MAKE_STR( _v ) MAKE_STR_X( _v )
    
    char *method() { return MAKE_STR( TYPE_NAME ); }
    

    【讨论】:

      【解决方案4】:

      你必须添加一层额外的宏:

      #define STRINGIZE(x) STRINGIZE2(x)
      #define STRINGIZE2(x) #x
      
      #define TOKENPASTE(x, y) TOKENPASTE2(x, y)
      #define TOKENPASTE2(x, y) x ## y
      

      原因是当你有一个宏时,预处理器通常会在执行宏替换之前递归地扩展参数。但是,如果任何参数与字符串化操作符 # 或标记粘贴操作符 ## 一起使用,则它是 not 扩展的。因此,您需要额外的一层宏,其中第一层扩展参数,第二层执行字符串化或标记粘贴。

      如果需要多次扩展参数(例如 #define A B, #define B C, #define C D, STRINGIZE(A)),则需要在应用 # 或 ## 运算符之前添加更多层。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多