C++无虚拟功能的动态调度

发布于 2024-10-12 08:48:14 字数 2309 浏览 6 评论 0原文

我有一些遗留代码,它们使用 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 语句。

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

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

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

I've got some legacy code that, instead of virtual functions, uses a kind field to do dynamic dispatch. It looks something like this:

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

and then the POD class's function members are implemented like this:

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(), and other members are implemented similarly.

This example is simplified. The actual code has about a dozen different subtypes of POD, and a couple dozen methods. New subtypes of POD and new methods are added pretty frequently, and so every time we do that, we have to update all these switch statements.

The typical way to handle this would be to declare the function members virtual in the POD class, but we can't do that because the objects reside in shared memory. There is a lot of code that depends on these structs being plain-old-data, so even if I could figure out some way to have virtual functions in shared-memory objects, I wouldn't want to do that.

So, I'm looking for suggestions as to the best way to clean this up so that all the knowledge of how to call the subtype methods is centralized in one place, rather than scattered among a couple dozen switch statements in a couple dozen functions.

What occurs to me is that I can create some sort of adapter class that wraps a POD and uses templates to minimize the redundancy. But before I start down that path, I'd like to know how others have dealt with this.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

小耗子 2024-10-19 08:48:14

您可以使用跳转表。这就是大多数虚拟调度的内部结构,您可以手动构建它。

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。我只是用它作为例子。我应该把它改回来。事实上,这个解决方案非常灵活,但重要的是,复制特定于进程的数据,例如函数指针或其他什么,然后将其传入。

You can use a jump table. This is what most virtual dispatches look like under the hood, and you CAN construct it manually.

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);
}

For a short example.

What exactly are the limitations of being in shared memory? I realized that I don't know enough here. Does it mean that I can't use pointers, because someone in another process will be trying to use those pointers?

You could use a string map, where each process gets it's own copy of the map. You'd have to pass this in to GetFoo() so that it can find it.

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

Edit: Of course, you don't have to use a string here, you could use an int. I just used it as example. I should change it back. Infact, this solution is pretty flexible, but the important thing is, make a copy of process-specific data, e.g. function pointers or whatever, and then pass it in.

你在看孤独的风景 2024-10-19 08:48:14

这是我现在要走的模板元编程路径。我喜欢它的原因如下:

  • 添加对新种类的支持只需要更新 LAST_KIND 并添加新的 KindTraits
  • 有一个添加新功能的简单模式。
  • 如有必要,可以将函数专门用于特定类型。
  • 如果我搞砸了任何事情,我可以期待编译时错误和警告,而不是神秘的运行时错误行为。

有几个问题:

  • POD 的实现现在依赖于所有派生类的接口。 (这在现有的实现中已经是正确的,所以我并不担心它,但它有点异味。)
  • 我指望编译器足够聪明,能够生成大致相当于 < 的代码基于 code>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);
}

Here's the template-metaprogramming path I'm going down now. Here is what I like about it:

  • Adding support for a new kind only requires updating LAST_KIND and adding a new KindTraits.
  • There is a simple pattern for adding a new function.
  • Functions can be specialized for particular kinds if necessary.
  • I can expect compile-time errors and warnings, rather than mysterious run-time misbehavior, if I screw anything up.

There are a couple of concerns:

  • POD's implementation is now dependent upon the interfaces of all the derived classes. (This is already true in the existing implementation, so I'm not worried about it, but it is a bit of a smell.)
  • I'm counting on the compiler to be smart enough to generate code that is roughly equivalent to the switch-based code.
  • Many C++ programmers will scratch their heads upon seeing this.

Here's the code:

// 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);
}
久隐师 2024-10-19 08:48:14

您可以尝试奇怪的重复模板模式。它有点复杂,但是当您无法使用纯虚函数时它会很有帮助。

You can experiment with Curiously recurring template pattern. It's a bit complicated, but when you cannot use pure virtual functions it can be helpful.

清晰传感 2024-10-19 08:48:14

这里有一种方法,使用虚方法来实现跳转表,而不要求 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 */ } 

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 */ } 
柒七 2024-10-19 08:48:14

这是一个使用奇怪的重复模板模式的示例。如果您在编译时了解更多信息,这可能会满足您的需求。

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;
}

Here is an example using Curiously recurring template pattern. This may suit your needs if you know more info at the compile time.

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;
}
初雪 2024-10-19 08:48:14

扩展您最终得到的解决方案,以下解决了程序初始化时到派生函数的映射:

#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';
}

Expanding on the solution you ended up with, the following solves the mapping to derived functions at program initialization:

#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';
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文