可変長テンプレート、低結合およびいくつかの考え

確かに、すべてのプログラマーは、プログラムの多くの部分で使用されるクラスのセット(おそらくサービス)がアプリケーションにあるという状況に直面していました。 そして、すべては大丈夫のように見えましたが、これらのクラスを変更する必要が生じたらすぐに、これは呼び出し元のコードに悪影響を与える可能性があります。 はい、タイトルに示されているように、この記事では非常に低いカップリングパターンに焦点を当てます。







問題は新しいものではなく、長い間知られています。 それを解決する方法はいくつかありますが、それはすべて対象分野に依存します。 私は読者に、適用されたタスクの実行中に見つけた可能な解決策を提供します。 理想主義者として、私が見つけた解決策は完全に満足できるものではありませんでした。 また、C ++ 11標準の新機能を活用したいという要望から、大部分が設計されました。 当然、書かれたものはすべて議論の対象となり、おそらく誰かがより一貫したバージョンを提供するでしょう。



タスクステートメント







システムにはさまざまなデバイスがあります。 たとえば、温度センサー、速度センサー、アラーム、信号機など。 読者が示唆するように、プログラム内のこれらのデバイスはすべて、基本クラスの相続人によって表されます。 もちろん、各相続人には独自の特定のメンバー関数があります。

-すべてはいつも通りです。 問題は何ですか?


問題は、いくつかのシステムがあることです。 このような各システムでは、デバイスの構成と機能の両方が変化する可能性があります(拡張、または逆に狭幅)。 あるシステムでは信号機がなく、他のシステムではすべてが最初のものと同じですが、アラームにはいくつかの動作モードがあります。 図で考えられるケースを引用しました。







最初のシステムに対するSyncBoard



の特定の実装は、速度要求メソッドを再定義するだけですが、2番目のシステムには新しいメソッドが追加されていることがSyncBoard



ます。 したがって、発信者は、使用する各デバイスの特定のタイプの知識を必要とします。 しかし、呼び出し元はシステムごとに変わりません。 その基本的なロジックは変更されず、デバイスとの対話方法のみが変更されます。

-もちろん、発信者がデバイスの新しい機能を使用する必要がある場合、その特定のタイプを知る必要があります。


しかし、反対側から見てみましょう。 特定のモジュール/クラスがあり、1つのシステムではSyncBoard_System1



タイプを使用し、2つ目のシステムではSyncBoard_System1



タイプをそれぞれSyncBoard_System2



ます。 上で言ったように、このモジュール/クラスはこれら2つのシステムのロジックを変更しません。 デバイスとの相互作用のみが変更されます。 オプションは何ですか?

1)このモジュールをコピーして、新しい対話方法を使用します。 異なるシステムでは、モジュールの異なるコピーを使用します。

-ありがとう、笑った。


2)別の仮想機能でデバイスと対話します。 相続人はデバイスとの相互作用を再定義します。

-より良い。 クラスが相互作用する各デバイスでのみ、仮想機能を実行しますか? 新しいデバイスでは、関連するクラスに新しい仮想関数を表示する必要があります。


3)別のクラスでデバイスとの相互作用をカプセル化し、それを戦略として使用します。

-スマートな本のようですね。 しかし、デバイスの新しい機能のためには、戦略もそれを実装する必要があります。 つまり、戦略のインターフェースが変更されますが、戦略の特定の実装(タイプ)を知る必要があります。


詳細は消えませんでしたが、接続の数は減りました。 特定のデバイス実装(多数ある)に関連付けられている各呼び出しモジュールの代わりに、これらのデバイスに呼び出しを委任する(戦略)を定義できます。

-メディエーターまたはファサードに非常に似ています。


外側のみ。 これらのパターンと私のタスクの共通の特徴は、多くのリンクの代わりに、単一のリンクが「」で作成されることです。 ここでは、特定のデバイスの実装をすべて隠し、それらとやり取りするためのインターフェースをクライアントに提供するクラスが必要であるという決定について説明します。



実装オプションを検索する



オプション1-額に。



マネージャーはすべてのデバイスのリストを保存し、適切なインターフェースを提供します。







-システムに複数の温度センサーがある場合は? getTemperature()



関数getTemperature()





公正な発言。 そのような各機能にパラメータを追加する必要があることがわかりました-デバイス識別子。 正解。







このオプションはすぐに思いつきました。 最も簡単です。 そして、これはおそらく彼の最後のプラスです。 新しいデバイスを追加するとき、何が待っているか想像してみましょう。 新しいデバイス自体のクラスを作成することに加えて、マネージャーでそのすべての機能を表示する必要があります(もちろん、それらを委任します)。 そして、このデバイスが突然すべてのシステムに突然追加された場合、すべてのシステムのマネージャーのすべての特定の実装にこれらすべての機能も追加します。 最も純粋な形の「コピーアンドペースト」。

-美しくはないが、致命的ではない。


別のシナリオ。 2つのシステムはまったく同じです。 それらは、単一のデバイスがない場合にのみ異なります。 この場合、記述されたマネージャークラスを使用できなくなりました。 必要のない機能が含まれています。 まあ、より正確には、私たちはできますが、これは間違っているという意見があります。 これらのメソッドを継承して空の実装に置き換えるか、クラスをコピーして余分なものを削除するかのいずれかです。 どちらも私にふさわしくない。



オプション2-case'ovの束。



しかし、マネージャーを委任が行われる単一の仮想メソッドにするとどうなりますか?

-関数のさまざまなパラメーターと戻り値の違いを忘れていませんか?


これを行うには、呼び出し元のコードがパラメーターを送受信するための基本データクラスを作成します。







実装例
 class IDevData { public: IDevData(); virtual ~IDevData(); virtual int getID() = 0; }; class DevManager_v2 : public IDeviceManager { public: bool initialize() { //    } //   virtual void callMethod(int dev_id, IDevData& data) { switch (data.getID()) { case DATA_SYNC_BOARD_GET_STATE: //      dev_id  getState(); //     IDevData break; case DATA_SYNC_BOARD_GET_VELOCITY: // ... break; // ... etc } } };
      
      







私たちは何を達成しましたか? 現在、仮想メソッドは1つしかありません。 すべての変更はシステム内でのみ発生し、システムからシステムにデバイスを追加/削除すると、不要な機能は自動的に消えます。 呼び出し元クラスは、特定のマネージャー実装を知る必要はまったくありません! 彼らはcallMethod



メソッドと...

-はい、はい! そして、各呼び出しの特定のタイプのIDevData



特定のデバイス実装へのバインディングから以前に実行した場合、 IDevData



ラッパーの特定の実装へのバインディングになりました。
面白いです。


ある種の悪循環。 私たちは始めたのと同じ場所に到着しました。 マネージャーから1つのメソッドを呼び出す前に、呼び出し元は作成するIDevData



タイプを正確に知る必要があります。 そして、これは、発信者が特定のタイプのデバイスを知っていた状況とどう違うのですか? はい、何もありません!



オプション3-C ++ 11



単一のcallMethod()



関数のアイデアが気に入りました。 しかし、パラメーターの受け渡しに関する問題により、すべての労力がゼロになりました。 この単一の関数に任意の量のパラメータを渡して、そこから任意のタイプの戻り値を取得できれば素晴らしいと思います...

-はい、皆さんはテンプレートとC ++ 11について話していることを既に理解しています。 さあ、宇宙船がどのように耕すのか教えてください...©


新しい標準は、そのような機会を提供するだけです。 callMethod



関数はボイラープレートであり、次のプロトタイプを持つ必要があることが明らかになりました。

 template <typename ResType, typename ... Args> ResType callMethod(int dev_id, Args&& ... args);
      
      





これで、呼び出し元のコードは、マネージャー、パラメーターの種類、および戻り値(既に当たり前と考えられている)以外の情報を知る必要がなくなりました!

-そして、1つのクラスに署名が同じ2つの関数がある場合、どのように状況を解決しますか? 思考のdev_id



から判断すると、どこか(どこ?)でdev_id



(特定のクラスを意味する)に連絡し、すべてのArgs&&…



パラメーターを誰か(誰に)に渡したいだけです。


実際、これは問題を引き起こします。 それを解決するための2つのオプションがあります。 別のパラメーターint method_id



を追加します。これはまったく好きではないか、パラメーターint dev_id



別の意味を与えます。 たとえば、 command_id



と呼びましょう。これは、特定のクラスの特定のメソッドを意味します。 つまり、ペアの特定の識別子はClass->メソッドです。 したがって、これらのcommand_id



の値は、すべてのデバイスクラスのメソッドとまったく同じになります。 もちろん、これは列挙に変換する必要がありますが、これに焦点を合わせることはしません。 ここで、「 command_id



への連絡先」と「 Args&&



を渡す相手」について説明します。 command_id



パラメーター自体からヒントが得られます。 command_id



によってアクセスされるメソッドの特定のコレクションを想定しています。 つまり、次のスキームが必要です。

1)署名付きの機能用のストレージを作成する

2) callMethod



command_id



キーを使用してストレージから必要なオブジェクトを削除し、すべてのパラメーターを渡します

3)利益!

-ありがとう、CEP。


ポイント1は、私の前にすでに解決されています。 特に、habrにはtype erasureに関する記事もありました。 私はそれを読み、自分のニーズに合わせて修正しました。 rpz他のソースに感謝します



怠けすぎたり、読み直したりする時間がない人のために、その仕組みを簡単に説明します。 まず、型チェックに便利な仮想関数を1つ持つ基本クラスが必要です。



 class base_impl { public: virtual std::type_info const& getTypeInfo() const = 0; };
      
      







次に、相続人-テンプレートクラスを作成します。 任意の関数を渡すことができます。 異なる関数(クラスメソッド、または単純な関数)に異なるテンプレートを作成しないために、既製のstd::function



を使用することにしました。 このクラスに必要なのは、呼び出しを委任するためのパラメーターが渡されるoperator()



をオーバーロードすることです。

Type_implテンプレート
 template <typename ResType, typename ... Args> class type_impl : public base_impl { typedef std::function<ResType(Args ...)> _Fn; _Fn _func; public: type_impl(std::function<ResType(Args ...)> func) : _func(func) {} std::type_info const& getTypeInfo() const { return typeid(_Fn); } ResType operator()(Args&& ... args) { return _func(std::forward<Args>(args)...); } };
      
      







現時点では、 base_impl



クラスへのポインターを使用してコンテナーに関数を追加できるスキームが既にあります。 しかし、このポインターを介してoperator()



呼び出す方法は? 型変換が必要です。 このために、 getTypeInfo()



メソッドがあります。 このインとアウト、および毎回手動で記述する必要性を隠すには、コンテナに関数を追加するときにtype_impl



テンプレートを使用し、最後のクラスをちょっとしたトリックで作成します。テンプレートコンストラクターです。

Funcwrapper
 class FuncWrapper { std::unique_ptr<base_impl> holder; public: template <typename ResType, typename ... Params> FuncWrapper(std::function<ResType(Params ...)> func) : holder(new type_impl<ResType, Params ...>(func)) {} ~FuncWrapper() {} template <typename ResType, typename ... Args> ResType call(Args&& ... args) { typedef std::function<ResType(Args ...)> _Fn; if (holder->getTypeInfo() != typeid(_Fn)) throw std::exception("Bad type cast"); type_impl<ResType, Args ...>* f = static_cast<type_impl<ResType, Args ...>*>(holder); return (*f)(std::forward<Args>(args)...); } } };
      
      







call()



テンプレートメソッドをそれにtype_impl



その中で保存されたtype_impl



呼び出しを委任します。

使用例
 class FuncWrapper { private: //      callable object. //    ,       class base_impl { public: virtual std::type_info const& getTypeInfo() const = 0; }; //      . //    -   base_impl. //       type_impl,   //     base_impl //      - ResType //    - Args... template <typename ResType, typename ... Args> class type_impl : public base_impl { typedef std::function<ResType(Args ...)> _Fn; _Fn _func; //       ! public: //    ,       std::function //     std::function ? type_impl(std::function<ResType(Args ...)> func) : _func(func) {} //    ,    . //   ,     . //   exception. std::type_info const& getTypeInfo() const { return typeid(_Fn); } //   , ,     . ResType operator()(Args&& ... args) { return _func(std::forward<Args>(args)...); } }; std::unique_ptr<base_impl> holder; public: //  ,      std::function //  ,        template <typename ResType, typename ... Params> FuncWrapper(std::function<ResType(Params ...)> func) : holder(new type_impl<ResType, Params ...>(func)) {} ~FuncWrapper() {} //  ,        template <typename ResType, typename ... Args> ResType call(Args&& ... args) { typedef std::function<ResType(Args ...)> _Fn; if (holder->getTypeInfo() != typeid(_Fn)) throw std::exception("Bad type cast"); //   ,    type_impl<ResType, Args ...>* f = static_cast<type_impl<ResType, Args ...>*>(holder.get()); return (*f)(std::forward<Args>(args)...); } }; // ,   test, -  class test { public: test() {} int fn1(int a) { cout << "test::fn1!!! " << a << endl; return ++a; } int fn2(int a, int b) { cout << "test::fn2!!! " << a << endl; return a + 2; } int fn3(int a, int b) { cout << "test::fn3!!! " << a << endl; return a + 3; } }; class IDeviceManager { protected: std::map<int, FuncWrapper*> m_funcs; public: virtual ~IDeviceManager() {}; virtual void initialize() = 0; template <typename ResType, typename ... Args> ResType callMethod(int command_id, Args&& ... args) { //     –    . return m_funcs[command_id]->call<ResType>(std::forward<Args>(args)...); } }; const int FN1_ID = 0; const int FN2_ID = 1; const int FN3_ID = 2; class DevManager_v3 : public IDeviceManager { std::unique_ptr<test> m_test_ptr; public: void initialize() { //    m_test_ptr.reset(new test); std::function<int(int)> _func1 = std::bind(&test::fn1, m_test_ptr.get(), std::placeholders::_1); std::function<int(int, int)> _func2 = std::bind(&test::fn2, m_test_ptr.get(), std::placeholders::_1, std::placeholders::_2); std::function<int(int, int)> _func3 = std::bind(&test::fn3, m_test_ptr.get(), std::placeholders::_1, std::placeholders::_2); //      m_funcs[FN1_ID] = new FuncWrapper(_func1); m_funcs[FN2_ID] = new FuncWrapper(_func2); m_funcs[FN3_ID] = new FuncWrapper(_func3); } ~DevManager_v3() { //    } }; int _tmain(int argc, _TCHAR* argv[]) { DevManager_v3 dev_manager; dev_manager.initialize(); // -!        . dev_manager.callMethod<int>(FN1_ID, 1); dev_manager.callMethod<int>(FN2_ID, 2, 3); dev_manager.callMethod<int>(FN3_ID, 4, 5); getchar(); }
      
      







わあ! 現在、1つの仮想メソッドinitialize()



があります。これは、特定のシステムに必要なすべてのデバイスを作成し、それらのメソッドをコレクションに入れます。 呼び出し元は、特定のタイプのマネージャーを知る必要さえありません。 callMethod()



テンプレートメソッドがすべてを行います。 特定のシステムごとに、たとえば<factory>を使用してIDevManager



の必要なインスタンスが作成されます。 呼び出し元に必要なのは、 IDevManager



の祖先へのポインタのみIDevManager





-目標を達成したようです。


はい。ただし、新しい欠点が現れ、おそらく最初の選択肢と比較して、より重大なマイナスの結果をもたらします。 コードは安全ではなくなります!

まず、 callMethod()



を注意深く見てください。 コレクションにないキーを渡すと、例外が発生します。 もちろん、最初にこのキーがコレクション内にあるかどうかを確認する必要があります。 しかし、キーが存在しないことが判明した場合(存在しないメソッドが要求された場合)はどうすればよいでしょうか? 例外を投げますか? そして最も重要なことは、コンパイル段階ではこれをキャッチできないことです。 これは、あるシステムまたはそのメソッドの一部にデバイスが存在しない場合に発生する可能性があります。

第二に、コードエディターは、入力callMethod()



でどのパラメーターが予期されるかを通知しません-パラメーターの名前/タイプ/数を表示しません。 間違ったタイプのパラメーターまたは間違った数のパラメーターを渡すと、例外が再びtest_impl



ますが、既にtest_impl



クラスのcall()



メソッドでtest_impl



ます。 繰り返しますが、コンパイルの段階でこれをキャッチすることはできません。 これは、プログラマーの不注意により容易に起こります。



タスクに関連して、次の理由により、私には向いていませんでした。

-コンパイル段階では、アクセスが必要なクラス(それぞれメソッド)の正確な数は常にわかっています。

-これらのクラスは、異なるシステムを設計する場合にのみ変更されます。

したがって、ゼロから始めなければなりませんでした。

-「ショー、また?!」©




オプション4-最終版?



私は彼に来て、とてもシンプルなデザインを見ました:

 template <class ... T> class Dissembler : public T ... { };
      
      





ここで何が起こっていますか? ただ多重継承。 しかし、それは私が必要なもののようです。 すべてのデバイスクラスを継承する場合、このクラスのすべてのメソッドを自動的に取得します。 3番目のオプションの欠点はなくなります。

-神聖なシンプルさ。 これは、2つの同一のクラスを継承する必要があるまで機能します。 または、2つのクラスに同一の関数があります(ダイヤモンド型の継承)。


したがって、メソッドが同じシグネチャを持っている場合でも、何らかの方法でメソッドを一意にする必要があります。 そして、私は手がかりがどこにあるかを知っていました。 アンドレイ、ありがとう!

ここは簡単です
 template <typename T, T t> struct ValueToType {}; template<typename C, class T> class ClassifiedWrapper {}; template<typename C, C c, class T> class ClassifiedWrapper<ValueToType<C, c>, T> : private T { public: ClassifiedWrapper(T&& fn) : T(std::forward<T>(fn)) {}; ClassifiedWrapper() = delete; virtual ~ClassifiedWrapper() {}; template <typename ... Args> std::result_of<T(Args...)>::type operator()(ValueToType<C, c>&&, Args&& ... args) { return T::operator()(std::forward<Args>(args) ...); }; };
      
      







ValueToType



クラスには、テンプレートパラメータの値に応じてタイプを設定するという1つの目的があります。 ClassifiedWrapper



クラスは、「呼び出し可能オブジェクト」の別のラッパーです。 その目的は、演算子operator()



ブラケットoperator()



が定義されているオブジェクトから継承し、呼び出しを委任することですが、「一意性」を導入する追加のパラメーターを使用します。 それは:

 class test { public: test() {} int fn1(int a) { cout << "test::fn1!!! " << a << endl; return ++a; } int fn2(int a, int b) { cout << "test::fn2!!! " << a << endl; return a + 2; } int fn3(int a, int b) { cout << "test::fn3!!! " << a << endl; return a + 3; } }; int _tmain(int argc, _TCHAR* argv[]) { test t; std::function<int(int, int)> _func2 = std::bind(&test::fn2, &t, std::placeholders::_1, std::placeholders::_2); ClassifiedWrapper<ValueToType<int, 0>, decltype(_func2)> cw1(std::ref(_func2)); ClassifiedWrapper<ValueToType<int, 1>, decltype(_func2)> cw2(std::ref(_func2)); cw1(ValueToType<int, 0>(), 2, 1); //  cw2(ValueToType<int, 1>(), 3, 4); //  cw1(ValueToType<int, 0>(), 1); //   cw2(ValueToType<short, 1>(), 3, 4); //   }
      
      







関数は同じですが、追加のパラメーターを使用して2つのラッパーを異なるものにしました。

-そして、この良いとどうする?


その結果、任意の関数をラップして一意にすることができるクラスがあります。 このオプションはどこから始まったのか、元の問題を覚えていますか? これで、複数の継承で同じフォーカスを適用できますが、 CalssifiedWrapper



から継承できます。

まず、空白を宣言します。
 template <class ... T> class Dissembler {};
      
      







次に、パラメーターパッケージの展開の再帰を同時に開始する部分的な特殊化を行います
 template <typename C, C c, class Func, class ... T> class Dissembler<ValueToType<C, c>, Func, T ...> : protected ClassifiedWrapper<ValueToType<C, c>, Func>, protected Dissembler<T ...> //   ,  Dissembler<T ...>     ValueToType<C, c>  Func   T ...,     .   ,    () { protected: using ClassifiedWrapper<ValueToType<C, c>, Func>::operator(); using Dissembler<T ...>::operator(); public: Dissembler(ValueToType<C, c>&& vt, Func&& func, T&& ... t) : ClassifiedWrapper<ValueToType<C, c>, Func>(std::forward<Func>(func)), Dissembler<T ...>(std::forward<T>(t) ...) {}; //       Dissembler() = delete; virtual ~Dissembler() {}; //  ,      . template <typename Cmd, Cmd cmd, typename ... Args> std::result_of<Func(Args...)>::type call(Args&& ... args) { //      ,   ClassifiedWrapper'. //   ,        return this->operator()(ValueToType<Cmd, cmd>(), std::forward<Args>(args)...); }; };
      
      







さて、再帰を停止するだけです
 template <typename C, C c, class Func> class Dissembler<ValueToType<C, c>, Func> : protected ClassifiedWrapper<ValueToType<C, c>, Func> { public: Dissembler(ValueToType<C, c>&&, Func&& func) : ClassifiedWrapper<ValueToType<C, c>, Func>(std::forward<Func>(func)) {}; Dissembler() = delete; virtual ~Dissembler() {}; };
      
      







-あなたは目を壊します。 指でわかりやすく説明できますか?


基本的な考え方は単純です-多重継承。 しかし、前述の問題(継承チェーン内の2つの同一クラスまたはダイヤモンド型の継承)に遭遇するとすぐに、すべてが機能しなくなります。これを行うために、クラス(ClassifiedWrapper



を作成します。クラス()は、「関数に一意のラベルを割り当てる」ことができます(実際、属性を持たないため、非常に美しく配置します)。同時に、それ自体ClassifiedWrapper



はもちろん、一意です(ここでも、テンプレートパラメーターが異なることは明らかです)。次に、にラップされたこのような一意の関数の「静的リスト」を作成し、ClassifiedWrapper



それらすべてから継承します。ふう、おそらく説明しやすいでしょう。一般に、Variadicテンプレートで適用した焦点は多くの場所で説明されています。特に、Habré

-そして、なぜ再帰クロージャにメソッドがないのcall



ですか?


それは、庭を1つの機能の周りにフェンスすることは意味がないからです。つまり、誰かがDissembler



多くの機能ではなく、1つの機能に使用したい場合、この考えは意味がありません。以下に、この経済をすべて使用する方法を示します。

方法
 int _tmain(int argc, _TCHAR* argv[]) { test t; std::function<int(int, int)> _func2 = std::bind(&test::fn2, &t, std::placeholders::_1, std::placeholders::_2); std::function<int(int, int)> _func3 = std::bind(&test::fn3, &t, std::placeholders::_1, std::placeholders::_2); Dissembler< ValueToType<int, 0>, decltype(_func2), ValueToType<char, 'a'>, decltype(_func3) > dis( ValueToType<int, 0>(), std::move(_func2), ValueToType<char, 'a'>(), std::move(_func3) ); dis.call<int, 0>(0, 1); dis.call<char, 'a'>(0, 1); getchar(); }
      
      







«» – ValueToType<int, 0>



ValueToType<char, 'a'>



. , «» int , . – call



, .

, , . , , , .

もちろん、経験の浅い目にとっても、テンプレートが嫌いな人にとっても、生成されたコンパイルエラーを見るのはショックです(パラメーターが正しく指定されていない場合)。しかし、これにより、エラーが注意を引き付け、実行時に発生しないことが保証されます。これは、実行する価値があると思います。

-そして、最終的にはどうなりIDevManager



ますか?


オプション3とほぼ同じですが、わずかな違いがあります。

 typedef Dissembler< ValueToType<int, 0>, std::function<int(int, int)>, ValueToType<int, 1>, std::function<int(int, int)> > SuperWrapper; class IDeviceManager { protected: std::unique_ptr<SuperWrapper> m_wrapperPtr; public: virtual ~IDeviceManager() {}; virtual void initialize() = 0; template <int command_id, typename ResType, typename ... Args> ResType callMethod(Args&& ... args) { return m_wrapperPtr->call<int, command_id>(std::forward<Args>(args)...); } }; class DevManager_v4 : public IDeviceManager { std::unique_ptr<test> m_test_ptr; public: void initialize() { //    m_test_ptr.reset(new test); std::function<int(int, int)> _func2 = std::bind(&test::fn2, m_test_ptr.get(), std::placeholders::_1, std::placeholders::_2); std::function<int(int, int)> _func3 = std::bind(&test::fn3, m_test_ptr.get(), std::placeholders::_1, std::placeholders::_2); m_wrapperPtr.reset(new SuperWrapper( ValueToType<int, 0>(), std::move(_func2), ValueToType<int, 1>(), std::move(_func3) )); } }; int _tmain(int argc, _TCHAR* argv[]) { DevManager_v4 v4; v4.initialize(); v4.callMethod<1, int>(0, 1); v4.callMethod<0, int>(10, 31); getchar(); }
      
      







定義SuperWrapper



(システムごとに)は、個別のヘッダーファイルに配置する必要があります。そして、それぞれの定義を#ifdef



'で区切って、正しいプロジェクトで正しい定義が接続されるようにしますSuperWrapper



このため、オプション4を作成するときに疑問符を付けています。



All Articles