プリプロセッサ定義に基づいてクラスのスタブまたはモックバージョンを作成したいと思います。プリプロセッサマクロが "enable"に設定されている場合、クラスは通常のクラスに過ぎません。 "disable"に設定されている場合、クラスは空のスタブであり、コンパイラは完全に離れて最適化することができます。しかし、実際のクラスで正常にコンパイルされたりコンパイルされなかったりするコードは、対応する振る舞いもスタブクラスで持つ必要があります。 isset()
の定義はif (a_foo.isset()) { code(); }
のようなコードは何にも離れて最適化することができるように意図されてC++クラスのスタブバージョンを作成する
class _foo {
public:
foo(int x) : x_(x) {}
void add(int x) { x_ += x; }
void add(const char *str) { x_ += atoi(str); }
bool isset(void) { return x_ > 0; }
private:
int x_;
};
#if ENABLE_FOO
using foo = _foo;
#else
class foo {
public:
foo(int x) {}
void add(int x) { return; }
void add(const char *str) { return; }
bool isset(void) { return false; }
};
#endif
:ここ
が、これがどのように見えるかの例です。明らかに、これは、どのメソッドおよびそのメソッドの使用に対しても普遍的には機能しません。 0、偽、NULLなどが無効な場合の合理的な戻り値になるようにクラスを設計する必要があります。
これはうまくいきますが、foo
のスタブバージョンを実際のバージョンと完全に同期させておく必要があります。どのメソッドへの変更もすべて、スタブに複製する必要があります。これは迷惑です。どのようにスタブをより自動化するのですか?理想的にはまたはSTUB(foo)
と書くことができ、スタブクラスはそれだけで作成されます。この目的を達成するために
、私がこれまで思い付くことができました:
_foo
のスタブバージョンを作成
class foo {
public:
CTOR_STUB(_foo, foo);
METHOD_STUB(_foo, add);
METHOD_STUB(_foo, isset);
};
。各メソッド名をリストする必要がありますが、戻り値の型も引数も引数の数も指定する必要はありません。すべての過負荷(すなわち、add()
の両方の方法)は、METHOD_STUB
でカバーされています。オーバーロードには異なる戻り値の型があります。スタブ付きメソッドがメソッドテンプレートの場合でも機能します。ここで
#define METHOD_STUB(base, func) \
template <typename... Args> \
auto func(Args... args) { \
using RetType = decltype(std::declval<base>().func(std::forward<Args>(args)...)); \
return (RetType)0; }
#define CTOR_STUB(base, name) \
template <typename... Args> \
name(Args... args) { return; base _dummy{std::forward<Args>(args)...}; }
アイデアはまだ、適切な引数や戻り値の型を持つメソッドが正常にコンパイルするために、スタブクラスに存在することを要求するテンプレートを定義することですコンパイラによって何も最適化されません。
マクロを避ける方法はありますか?これはテンプレートのみを使用しますか?メソッドの名前をテンプレートパラメータにしたいと思うようですが、私はそれを行う方法がわかりません。
CTOR_STUB()
に現在のクラスの名前を指定する必要性を避ける方法はありますか?コンパイラは名前を知っていますが、名前をコンストラクタテンプレートの定義に使用できるシンボルとして取得する方法や、クラス名をテキスト文字列または型として取得する方法はわかりません。
クラスの実際のバージョンが同じことをしないときに、スタブが正しくコンパイルされるか、コンパイルに失敗するといういくつかの欠陥がありますか?
シグネチャの変更を伝播することは、別々の定義と宣言を持つ一般的なクラス(つまり非テンプレート)の変更を伝播することとまったく同じではありません。それを忘れると、コンパイル時にエラーが発生します。私はここではマクロを使わなくてはならないというメリットは見られません。 –
'(RetType)0'は危険です。おそらく 'static_cast(0)'を意味します。 –
cdhowie
@NirFriedmanの違いは、署名が変わったときに更新する場所がもう1つありますが、条件付きコンパイルです。 "有効"の場合にスタブとビルドを更新することを忘れると、スタブが使用されないのでエラーは発生しません。また、クラスのユーザーを更新できない場合、「無効」の場合はエラーは発生しません。それらを自動的にリンクすることで、有効なケースと無効なケースの両方で同じコンパイル時間のチェックを受けることができます。 – TrentP