更新 显然,您希望使用模板或基类而不是宏来执行此操作。不幸的是,出于各种原因,我无法使用模板或基类。
目前我正在使用宏来定义各种类的字段和方法,如下所示:
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
文件包含原始宏的内容。但是,因为我是#including我可以在运行时进入代码,在调试时。
但是我在TYPE_NAME
文件中'{string}'和{token}粘贴FieldsNMethods.h
预处理器符号时遇到问题。
例如,我想在FieldsNMethods.h
中定义类的析构函数,因此需要使用TYPE_NAME
的值,如下所示:
~TYPE_NAME()
{
//...
}
但TYPE_NAME
替换为其值。
我正在尝试什么?我不能直接使用字符串化和令牌粘贴操作符,因为我不在宏定义中。
答案 0 :(得分: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"
};
答案 1 :(得分: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)
),那么在应用#或##运算符之前,需要添加更多层。
答案 2 :(得分: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 ); }
答案 3 :(得分:1)
不,您无法动态定义类或函数定义。必须通过直接键入或在预处理器中定义它们来指定它们。
通常,不需要生成这样的类,并且在编译之前创建类定义,无论是通过键入所有内容还是使用某种代码生成。有时会有一个单独的代码生成步骤(例如,在当前的Visual Studio中,您可以定义前处理和后处理步骤。)
现在,如果您需要为不同的数据类型创建某些类的不同版本,则可以使用模板。你不能用这种方式创建不同名称的橡皮图章类。
最后一个问题:你为什么要这样做?我从来没有处过这样的地方,在这种情况下,这样的东西在C ++中看起来很有用,并且在那些确实有意义的语言中有设施可以做到。