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()和其他成员的实现方式类似。

这个例子被简化了。实际的代码有十几种不同的子类型POD和几十种方法。新的子类型POD和新方法的添加非常频繁,因此每次我们这样做时,我们都必须更新所有这些switch语句。

处理这种情况的典型方法是virtualPOD类中声明函数成员,但我们不能这样做,因为对象驻留在共享内存中。有很多代码依赖于这些结构是普通的旧数据,所以即使我能找到某种方法在共享内存对象中拥有虚函数,我也不想这样做。

因此,我正在寻找有关清理它的最佳方法的建议,以便所有关于如何调用子类型方法的知识都集中在一个地方,而不是分散在几十switch个函数中的几十个语句中。

我想到的是,我可以创建某种包装 aPOD并使用模板来最小化冗余的适配器类。但在我开始走这条路之前,我想知道其他人是如何处理这个问题的。

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

Here is an approach that uses virtual methods to implement the jump table, without requiring the Pod class or the derived classes to actually have virtual functions.

The objective is to simplify adding and removing methods across many classes.

To add a method, it needs to be added to Pod using a clear and common pattern, a pure virtual function needs to be added to PodInterface, and a forwarding function must be added to PodFuncs using a clear and common pattern.

Derived classes need only have a file static initialisation object to set things up, otherwise look pretty much like they already do.

// 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

这是我现在要走的模板元编程路径。这是我喜欢它的地方:

  • 添加对新类型的支持只需要更新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 回答
1

您可以尝试使用Curiously recurring 模板模式。这有点复杂,但是当你不能使用纯虚函数时,它会很有帮助。

于 2011-01-14T16:35:49.943 回答
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 回答