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の「信頼できる唯一の情報源」の原則(またはDRY「Do n'tRepeat Yourself」)を使用して、質問を改善する必要があると思います。

#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

その理由は、マクロがある場合、プリプロセッサは通常、マクロ置換を実行する前に引数を再帰的に展開するためです。ただし、引数が文字列化演算子 # またはトークン貼り付け演算子 ## で使用されている場合、展開されません。したがって、追加のマクロ レイヤーが必要です。最初のレイヤーは引数を展開し、2 番目のレイヤーは文字列化またはトークンの貼り付けを実行します。

引数を複数回展開する必要がある場合 ( など#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 に答える