没有虚函数的C++动态调度

Kri*_*son 12 c++ refactoring metaprogramming template-meta-programming

我有一些遗留代码,而不是虚函数,使用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 ...
Run Code Online (Sandbox Code Playgroud)

然后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");
    }
}
Run Code Online (Sandbox Code Playgroud)

POD::GetBar(),POD::GetBaz(),POD::GetXyzzy(),和其他部件被类似地实现.

这个例子很简单.实际的代码有十几种不同的子类型POD,以及几十种方法.POD经常添加新的子类型和新方法,因此每次我们这样做时,我们都必须更新所有这些switch语句.

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

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

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

Pup*_*ppy 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);
}
Run Code Online (Sandbox Code Playgroud)

举个简短​​的例子.

共享内存的限制究竟是什么?我意识到我在这里不够了解.这是否意味着我不能使用指针,因为另一个进程中的某个人会尝试使用这些指针?

您可以使用字符串映射,其中每个进程都获取它自己的映射副本.你必须将它传递给GetFoo(),以便它可以找到它.

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

编辑:当然,你不必在这里使用字符串,你可以使用int.我只是用它作为例子.我应该改回来.事实上,这个解决方案非常灵活,但重要的是,制作特定于流程的数据的副本,例如函数指针或其他,然后传递它.

  • 我没有查看细节,但一般来说,表由每个对象中的指针引用.如果线程在地址Y处具有类型X的vtable,则它将Y存储在对象的vptr字段中.即使vtable存储在系统中完全相同的硬件地址中,也不能保证两个不同的进程不会在不同的逻辑地址中看到它.如果是这种情况,其他线程将尝试在错误的地址中使用vtable并死掉. (2认同)