C++无虚拟功能的动态调度
我有一些遗留代码,它们使用 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
您可以使用跳转表。这就是大多数虚拟调度的内部结构,您可以手动构建它。
举一个简短的例子。
共享内存到底有哪些限制?我意识到我在这里了解得还不够。这是否意味着我不能使用指针,因为另一个进程中的某人会尝试使用这些指针?
您可以使用字符串映射,其中每个进程都会获取自己的映射副本。您必须将其传递给 GetFoo() 以便它可以找到它。
编辑:当然,您不必在这里使用字符串,您可以使用 int。我只是用它作为例子。我应该把它改回来。事实上,这个解决方案非常灵活,但重要的是,复制特定于进程的数据,例如函数指针或其他什么,然后将其传入。
You can use a jump table. This is what most virtual dispatches look like under the hood, and you CAN construct it manually.
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.
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.
这是我现在要走的模板元编程路径。我喜欢它的原因如下:
LAST_KIND
并添加新的KindTraits
。有几个问题:
POD
的实现现在依赖于所有派生类的接口。 (这在现有的实现中已经是正确的,所以我并不担心它,但它有点异味。)这是代码:
Here's the template-metaprogramming path I'm going down now. Here is what I like about it:
LAST_KIND
and adding a newKindTraits
.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.)switch
-based code.Here's the code:
您可以尝试奇怪的重复模板模式。它有点复杂,但是当您无法使用纯虚函数时它会很有帮助。
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.
这里有一种方法,使用虚方法来实现跳转表,而不要求 Pod 类或派生类实际上具有虚函数。
目的是简化在许多类中添加和删除方法的过程。
要添加方法,需要使用清晰通用的模式将其添加到 Pod,需要使用清晰通用的模式将纯虚函数添加到 PodInterface,并且必须使用清晰通用的模式将转发函数添加到 PodFuncs。
派生类只需要一个文件静态初始化对象来进行设置,否则看起来与它们已经做的非常相似。
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.
这是一个使用奇怪的重复模板模式的示例。如果您在编译时了解更多信息,这可能会满足您的需求。
Here is an example using Curiously recurring template pattern. This may suit your needs if you know more info at the compile time.
扩展您最终得到的解决方案,以下解决了程序初始化时到派生函数的映射:
Expanding on the solution you ended up with, the following solves the mapping to derived functions at program initialization: