18

ポインター/参照/メンバーへのポインター (非型) テンプレートパラメーターを使用したことがありますか?
私は、その C++ 機能をベスト プラクティスとして使用する必要がある (健全な/現実世界の) シナリオを認識していません。

機能のデモンストレーション (ポインター用):

template <int* Pointer> struct SomeStruct {};
int someGlobal = 5;
SomeStruct<&someGlobal> someStruct; // legal c++ code, what's the use?

どんな啓発も大歓迎です!

4

5 に答える 5

13

関数へのポインタ:

メンバー関数へのポインターおよび関数へのポインターの非型パラメーターは、一部のデリゲートにとって非常に便利です。これにより、非常に高速なデリゲートを作成できます。

元:

#include <iostream>
struct CallIntDelegate
{
    virtual void operator()(int i) const = 0;
};

template<typename O, void (O::*func)(int)>
struct IntCaller : public CallIntDelegate
{
    IntCaller(O* obj) : object(obj) {}
    void operator()(int i) const
    {
        // This line can easily optimized by the compiler
        // in object->func(i) (= normal function call, not pointer-to-member call)
        // Pointer-to-member calls are slower than regular function calls
        (object->*func)(i);
    }
private:
    O* object;
};

void set(const CallIntDelegate& setValue)
{
    setValue(42);
}

class test
{
public:
    void printAnswer(int i)
    {
        std::cout << "The answer is " << 2 * i << "\n";
    }
};

int main()
{
    test obj;
    set(IntCaller<test,&test::printAnswer>(&obj));
}

ライブの例はこちら.

データへのポインタ:

このような非型パラメーターを使用して、変数の可視性を拡張できます。

たとえば、リフレクション ライブラリ (スクリプト作成に非常に役立つ可能性があります) をコーディングしている場合、マクロを使用して、ユーザーがライブラリのクラスを宣言できるようにするには、すべてのデータを複雑な構造 (時間の経過とともに変化する可能性があります) に格納することをお勧めします。 )、それを使用するハンドルが必要です。

例:

#include <iostream>
#include <memory>

struct complex_struct
{
    void (*doSmth)();
};

struct complex_struct_handle
{
    // functions
    virtual void doSmth() = 0;
};

template<complex_struct* S>
struct csh_imp : public complex_struct_handle
{
    // implement function using S
    void doSmth()
    {
        // Optimization: simple pointer-to-member call,
        // instead of:
        // retrieve pointer-to-member, then call it.
        // And I think it can even be more optimized by the compiler.
        S->doSmth();
    }
};

class test
{
    public:
        /* This function is generated by some macros
           The static variable is not made at class scope
           because the initialization of static class variables
           have to be done at namespace scope.

           IE:
               class blah
               {
                   SOME_MACRO(params)
               };
           instead of:
               class blah
               {
                   SOME_MACRO1(params)
               };
               SOME_MACRO2(blah,other_params);

           The pointer-to-data template parameter allows the variable
           to be used outside of the function.
        */
        std::auto_ptr<complex_struct_handle> getHandle() const
        {
            static complex_struct myStruct = { &test::print };
            return std::auto_ptr<complex_struct_handle>(new csh_imp<&myStruct>());
        }
        static void print()
        {
            std::cout << "print 42!\n";
        }
};

int main()
{
    test obj;
    obj.getHandle()->doSmth();
}

申し訳ありませんがauto_ptrshared_ptrは Codepad でも Ideone でも利用できません。 実例

于 2012-11-04T17:40:48.920 に答える
8

メンバーへのポインターの場合は、データまたは参照へのポインターとは大きく異なります。

テンプレート パラメーターとしてのメンバーへのポインターは、呼び出すメンバー関数 (またはアクセスするデータ メンバー) を指定したいが、オブジェクトを特定の階層に配置したくない場合に役立ちます (それ以外の場合は、通常は仮想メソッドで十分です)。 .

例えば:

#include <stdio.h>

struct Button
{
    virtual ~Button() {}
    virtual void click() = 0;
};

template<class Receiver, void (Receiver::*action)()>
struct GuiButton : Button
{
    Receiver *receiver;
    GuiButton(Receiver *receiver) : receiver(receiver) { }
    void click() { (receiver->*action)(); }
};

// Note that Foo knows nothing about the gui library    
struct Foo
{
    void Action1() { puts("Action 1\n"); }
};

int main()
{
    Foo foo;
    Button *btn = new GuiButton<Foo, &Foo::Action1>(&foo);
    btn->click();
    return 0;
}

グローバル オブジェクトへのポインタまたは参照は、テンプレートのインスタンス化が一定の (読み込み時に解決される) アドレスを使用して指定されたオブジェクトにアクセスし、そのような間接アクセスを使用しないため、アクセスに追加のランタイム コストを支払いたくない場合に役立ちます。通常のポインターまたは参照を使用すると発生します。ただし、支払う代償は、各オブジェクトの新しいテンプレートのインスタンス化であり、実際、これが役立つ可能性がある現実のケースを考えるのは困難です。

于 2012-11-04T17:49:12.457 に答える
4

パフォーマンス TRには、非型テンプレートを使用してハードウェアへのアクセス方法を抽象化する例がいくつかあります (ハードウェアの話は 90 ページから始まります。テンプレート引数としてのポインターの使用は、たとえば 113 ページにあります)。たとえば、メモリ マップド I/O レジスタは、ハードウェア領域への固定ポインタを使用します。私自身は使用したことがありませんが (Jan Kristofferson にその方法を示しただけです)、いくつかの組み込みデバイスの開発に使用されていることは確かです。

于 2012-11-04T17:41:55.493 に答える
4

SFINAE を活用するためにポインター テンプレート引数を使用するのが一般的です。std::enable_ifこれは、再定義エラーが発生するため、既定の引数を使用できない 2 つの同様のオーバーロードがある場合に特に便利です。

このコードは再定義エラーを引き起こします:

template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
void foo (T x)
{
    cout << "integral"; 
}

template <typename T, typename = std::enable_if_t<std::is_floating_point<T>::value>>
void foo (T x)
{
    cout << "floating";
}

しかし、有効なコンストラクトがデフォルトでstd::enable_if_t折りたたまれるという事実を利用するこのコードは問題ありません。void

                      // This will become void* = nullptr
template <typename T, std::enable_if_t<std::is_integral<T>::value>* = nullptr>
void foo (T x)
{
    cout << "integral"; 
}

template <typename T, std::enable_if_t<std::is_floating_point<T>::value>* = nullptr>
void foo (T x)
{
    cout << "floating";
}
于 2015-06-17T09:37:46.420 に答える
2

場合によっては、関数ポインターとして特定のシグネチャを持つコールバック関数を提供する必要がありますが (例: void (*)(int))、提供したい関数は (互換性はありますが) 異なるパラメーター (例: double my_callback(double x)) を取るため、そのアドレスを直接渡すことはできません。さらに、関数を呼び出す前と後に何らかの作業を行いたい場合があります。

関数ポインターを隠して、その関数または他のメンバー関数内からそれを呼び出すクラス テンプレートを作成するのは簡単ですoperator()()が、これでは通常の関数ポインターを抽出する方法が提供されません。thisコールバック関数を見つけるためのポインター。

入力関数を指定すると、カスタマイズされた静的メンバー関数を生成するアダプターを作成することにより、この問題を洗練された型安全な方法で解決できます (通常の関数と同様に、非静的メンバー関数とは異なり、そのアドレスを取得し、関数ポインターに使用されます)。 コールバック関数の情報を静的メンバー関数に埋め込むには、関数ポインター テンプレート パラメーターが必要です。 ここではそのテクニックを紹介します。

于 2012-11-04T18:27:43.140 に答える