12

kind仮想関数の代わりにフィールドを使用して動的ディスパッチを行う従来のコードがあります。次のようになります。

// Base struct shared by all subtypes
// Plain-old data; can't use virtual functions
struct POD
{
    int kind;

    int GetFoo();
    int GetBar();
    int GetBaz();
    int GetXyzzy();
};

enum Kind { Kind_Derived1, Kind_Derived2, Kind_Derived3 /* , ... */ };

struct Derived1: POD
{
    Derived1(): kind(Kind_Derived1) {}

    int GetFoo();
    int GetBar();
    int GetBaz();
    int GetXyzzy();

    // ... plus other type-specific data and function members ...
};

struct Derived2: POD
{
    Derived2(): kind(Kind_Derived2) {}

    int GetFoo();
    int GetBar();
    int GetBaz();
    int GetXyzzy();

    // ... plus other type-specific data and function members ...
};

struct Derived3: POD
{
    Derived3(): kind(Kind_Derived3) {}

    int GetFoo();
    int GetBar();
    int GetBaz();
    int GetXyzzy();

    // ... plus other type-specific data and function members ...
};

// ... and so on for other derived classes ...

PODクラスの関数メンバーは次のように実装されます。

int POD::GetFoo()
{
    // Call kind-specific function
    switch (kind)
    {
    case Kind_Derived1:
        {
        Derived1 *pDerived1 = static_cast<Derived1*>(this);
        return pDerived1->GetFoo();
        }
    case Kind_Derived2:
        {
        Derived2 *pDerived2 = static_cast<Derived2*>(this);
        return pDerived2->GetFoo();
        }
    case Kind_Derived3:
        {
        Derived3 *pDerived3 = static_cast<Derived3*>(this);
        return pDerived3->GetFoo();
        }

    // ... and so on for other derived classes ...

    default:
        throw UnknownKindException(kind, "GetFoo");
    }
}

POD::GetBar()POD::GetBaz()POD::GetXyzzy()、および他のメンバーも同様に実装されます。

この例は単純化されています。実際のコードには、 の約 12 の異なるサブタイプPODと、数十のメソッドがあります。の新しいサブタイプPODと新しいメソッドがかなり頻繁に追加されるため、そのたびにこれらすべてのswitchステートメントを更新する必要があります。

virtualこれを処理する一般的な方法は、クラスで関数メンバーを宣言することPODですが、オブジェクトが共有メモリに存在するため、これを行うことはできません。これらの構造体が単純な古いデータであることに依存するコードがたくさんあるため、共有メモリ オブジェクトで仮想関数を使用する方法を見つけられたとしても、それはしたくありません。

switchそのため、サブタイプ メソッドの呼び出し方法に関するすべての知識が、数ダースの関数内の数ダースのステートメントに分散するのではなく、1 か所に集中するように、これをクリーンアップする最善の方法に関する提案を探しています。

私が思いつくのはPOD、冗長性を最小限に抑えるためにテンプレートをラップして使用するある種のアダプター クラスを作成できるということです。しかし、その道を歩み始める前に、他の人がこれにどのように対処したかを知りたい.

4

6 に答える 6

12

ジャンプ台が使えます。これは、ほとんどの仮想ディスパッチが内部でどのように見えるかであり、手動で構築できます。

template<typename T> int get_derived_foo(POD*ptr) {
    return static_cast<T>(ptr)->GetFoo();
}
int (*)(POD*) funcs[] = {
    get_derived_foo<Derived1>,
    get_derived_foo<Derived2>,
    get_derived_foo<Derived3>
};
int POD::GetFoo() {
    return funcs[kind](this);
}

短い例です。

共有メモリにいることの制限は正確には何ですか? ここで自分の知識が足りないことに気づきました。別のプロセスの誰かがそれらのポインターを使用しようとするため、ポインターを使用できないということですか?

各プロセスが独自のマップのコピーを取得する文字列マップを使用できます。これを GetFoo() に渡して、見つけられるようにする必要があります。

struct POD {
    int GetFoo(std::map<int, std::function<int()>& ref) {
        return ref[kind]();
    }
};

編集: もちろん、ここで文字列を使用する必要はありません。int を使用できます。例として使用しただけです。私はそれを元に戻す必要があります。実際、このソリューションは非常に柔軟ですが、重要なことは、プロセス固有のデータ (関数ポインターなど) のコピーを作成し、それを渡すことです。

于 2011-01-14T16:34:16.053 に答える
1

これは、仮想メソッドを使用してジャンプ テーブルを実装するアプローチであり、Pod クラスまたは派生クラスに実際に仮想関数を持たせる必要はありません。

目的は、多くのクラスにわたるメソッドの追加と削除を簡素化することです。

メソッドを追加するには、明確で共通のパターンを使用して Pod に追加する必要があり、PodInterface に純粋仮想関数を追加する必要があり、明確で共通のパターンを使用して転送関数を PodFuncs に追加する必要があります。

派生クラスは、ファイルの静的初期化オブジェクトを設定するだけで済みます。それ以外の場合は、既に行っているように見えます。

// Pod header

#include <boost/shared_ptr.hpp>
enum Kind { Kind_Derived1, Kind_Derived2, Kind_Derived3 /* , ... */ };

struct Pod
{
    int kind;

    int GetFoo();
    int GetBar();
    int GetBaz();
};

struct PodInterface
{
    virtual ~PodInterface();

    virtual int GetFoo(Pod* p) const = 0;
    virtual int GetBar(Pod* p) const = 0;
    virtual int GetBaz(Pod* p) const = 0;

    static void
    do_init(
            boost::shared_ptr<PodInterface const> const& p,
            int kind);
};

template<class T> struct PodFuncs : public PodInterface
{
    struct Init
    {
        Init(int kind)
        {
            boost::shared_ptr<PodInterface> t(new PodFuncs);
            PodInterface::do_init(t, kind);
        }
    };

    ~PodFuncs() { }

    int GetFoo(Pod* p) const { return static_cast<T*>(p)->GetFoo(); }
    int GetBar(Pod* p) const { return static_cast<T*>(p)->GetBar(); }
    int GetBaz(Pod* p) const { return static_cast<T*>(p)->GetBaz(); }
};


//
// Pod Implementation
//

#include <map>

typedef std::map<int, boost::shared_ptr<PodInterface const> > FuncMap;

static FuncMap& get_funcmap()
{
    // Replace with other approach for static initialisation order as appropriate.
    static FuncMap s_funcmap;
    return s_funcmap;
}

//
// struct Pod methods
//

int Pod::GetFoo()
{
    return get_funcmap()[kind]->GetFoo(this);
}

//
// struct PodInterface methods, in same file as s_funcs
//

PodInterface::~PodInterface()
{
}

void
PodInterface::do_init(
        boost::shared_ptr<PodInterface const> const& p,
        int kind)
{
    // Could do checking for duplicates here.
    get_funcmap()[kind] = p;
}

//
// Derived1
//

struct Derived1 : Pod
{
    Derived1() { kind = Kind_Derived1; }

    int GetFoo();
    int GetBar();
    int GetBaz();

    // Whatever else.
};

//
// Derived1 implementation
//

static const PodFuncs<Derived1>::Init s_interface_init(Kind_Derived1);

int Derived1::GetFoo() { /* Implement */ }
int Derived1::GetBar() { /* Implement */ }
int Derived1::GetBaz() { /* Implement */ } 
于 2011-01-15T03:53:35.180 に答える
1

Curiously recurring template patternを試すことができます。少し複雑ですが、純粋仮想関数を使用できない場合に役立ちます。

于 2011-01-14T16:35:49.943 に答える
1

これが、私が今行っているテンプレート メタプログラミング パスです。これが私が気に入っているところです:

  • 新しい種類のサポートを追加するには、新しい を更新LAST_KINDして追加するだけKindTraitsです。
  • 新しい関数を追加するための簡単なパターンがあります。
  • 必要に応じて、関数を特定の種類に特化することができます。
  • 何かを台無しにすると、不可解な実行時の誤動作ではなく、コンパイル時のエラーと警告が予想されます。

いくつかの懸念があります。

  • PODの実装は、すべての派生クラスのインターフェイスに依存するようになりました。(これは既存の実装ですでにそうなっているので気にならない程度ですが、ちょっと臭いです。)
  • 私は、コンパイラが をswitchベースにしたコードとほぼ同等のコードを生成できるほど賢くなることを期待しています。
  • 多くの C++ プログラマーは、これを見て頭を悩ませます。

コードは次のとおりです。

// Declare first and last kinds
const int FIRST_KIND = Kind_Derived1;
const int LAST_KIND = Kind_Derived3;

// Provide a compile-time mapping from a kind code to a subtype
template <int KIND>
struct KindTraits
{
    typedef void Subtype;
};
template <> KindTraits<Kind_Derived1> { typedef Derived1 Subtype; };
template <> KindTraits<Kind_Derived2> { typedef Derived2 Subtype; };
template <> KindTraits<Kind_Derived3> { typedef Derived3 Subtype; };

// If kind matches, then do the appropriate typecast and return result;
// otherwise, try the next kind.
template <int KIND>
int GetFooForKind(POD *pod)
{
    if (pod->kind == KIND)
        return static_cast<KindTraits<KIND>::Subtype>(pod)->GetFoo();
    else
        return GetFooForKind<KIND + 1>();  // try the next kind
}

// Specialization for LAST_KIND+1 
template <> int GetFooForKind<LAST_KIND + 1>(POD *pod)
{
    // kind didn't match anything in FIRST_KIND..LAST_KIND
    throw UnknownKindException(kind, "GetFoo");
}

// Now POD's function members can be implemented like this:

int POD::GetFoo()
{
    return GetFooForKind<FIRST_KIND>(this);
}
于 2011-01-16T14:37:53.637 に答える
0

これは、Curiously recurring テンプレート パターンを使用した例です。コンパイル時に詳細な情報がわかっている場合、これはニーズに合っている可能性があります。

template<class DerivedType>
struct POD
{
    int GetFoo()
    {
        return static_cast<DerivedType*>(this)->GetFoo();
    }
    int GetBar()
    {
        return static_cast<DerivedType*>(this).GetBar();
    }
    int GetBaz()
    {
        return static_cast<DerivedType*>(this).GetBaz();
    }
    int GetXyzzy()
    {
        return static_cast<DerivedType*>(this).GetXyzzy();
    }
};

struct Derived1 : public POD<Derived1>
{
    int GetFoo()
    {
        return 1;
    }
    //define all implementations
};

struct Derived2 : public POD<Derived2>
{
    //define all implementations

};

int main()
{
    Derived1 d1;
    cout << d1.GetFoo() << endl;
    POD<Derived1> *p = new Derived1;
    cout << p->GetFoo() << endl;
    return 0;
}
于 2011-01-14T19:28:31.077 に答える
0

あなたが最終的に得たソリューションを拡張すると、以下はプログラムの初期化時の派生関数へのマッピングを解決します:

#include <typeinfo>
#include <iostream>
#include <functional>
#include <vector>

enum Kind
{
    Kind_First,
    Kind_Derived1 = Kind_First,
    Kind_Derived2,
    Kind_Total
};

struct POD
{
    size_t kind;

    int GetFoo();
    int GetBar();
};

struct VTable
{
    std::function<int(POD*)> GetFoo;
    std::function<int(POD*)> GetBar;
};

template<int KIND>
struct KindTraits
{
    typedef POD KindType;
};

template<int KIND>
void InitRegistry(std::vector<VTable> &t)
{
    typedef typename KindTraits<KIND>::KindType KindType;

    size_t i = KIND;
    t[i].GetFoo = [](POD *p) -> int {
        return static_cast<KindType*>(p)->GetFoo();
    };
    t[i].GetBar = [](POD *p) -> int {
        return static_cast<KindType*>(p)->GetBar();
    };

    InitRegistry<KIND+1>(t);
}
template<>
void InitRegistry<Kind_Total>(std::vector<VTable> &t)
{
}

struct Registry
{
    std::vector<VTable> table;

    Registry()
    {
        table.resize(Kind_Total);
        InitRegistry<Kind_First>(table);
    }
};

Registry reg;

int POD::GetFoo() { return reg.table[kind].GetFoo(this); }
int POD::GetBar() { return reg.table[kind].GetBar(this); }

struct Derived1 : POD
{
    Derived1() { kind = Kind_Derived1; }

    int GetFoo() { return 0; }
    int GetBar() { return 1; }
};
template<> struct KindTraits<Kind_Derived1> { typedef Derived1 KindType; };

struct Derived2 : POD
{
    Derived2() { kind = Kind_Derived2; }

    int GetFoo() { return 2; }
    int GetBar() { return 3; }
};
template<> struct KindTraits<Kind_Derived2> { typedef Derived2 KindType; };

int main()
{
    Derived1 d1;
    Derived2 d2;
    POD *p;

    p = static_cast<POD*>(&d1);
    std::cout << p->GetFoo() << '\n';
    p = static_cast<POD*>(&d2);
    std::cout << p->GetBar() << '\n';
}
于 2013-10-02T16:04:32.503 に答える