2

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


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

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,所以在调试时我可以在运行时单步执行代码。

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

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

~TYPE_NAME()
{
  //...
}

但用TYPE_NAME它的价值代替。

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

4

4 回答 4

6

这需要一个模板。

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 的其他评论表明问题仍然存在。让我们把它具体化。

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

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"
};
于 2008-12-05T14:47:42.347 回答
5

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

#define STRINGIZE(x) STRINGIZE2(x)
#define STRINGIZE2(x) #x

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

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

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

于 2008-12-05T15:41:51.223 回答
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 ); }
于 2008-12-05T15:10:39.727 回答
1

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

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

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

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

于 2008-12-05T14:55:25.500 回答