C++ - &CRTP.类型擦除vs多态

Din*_*aiz 8 c++ metaprogramming type-erasure

好的,我们走了.我正在尝试使用CRTP模板,以便从我的应用程序中删除多态性的需要.我使用像下面那样的方法

template <RealType> class Base 
{

    void doSomething()
    {
         static_cast<RealType>(this)->doSomethingImpl()
    }

class Derived1 : public Base
{
    void doSomethingImpl()
    {
        /* do something, for real */
    }
}

class Derived2 : public Base
{
    void doSomethingImpl()
    {
        /* do something else */
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我理解正确的话,这个方法允许我的类没有vtable,所以函数调用是直接的,不需要额外的间接.

现在假设我想将所有Derived#类存储在容器中.让我们说一个向量.

第一种方法:我可以创建一个非模板SuperBase类,Base从该类继承并将其存储在容器中.

然而,在我看来,如果我想这样做,我将不得不在SuperBase中使doSomething虚拟化.我的目标主要是没有vtable.

第二种方法:我使用类型擦除,即boost :: any之类的东西将我的元素存储在Vector中.但是,我没有看到我可以迭代元素并在它们上调用doSomething的方法,因为要"使用"boost :: any,我需要在迭代时知道对象的真实类型.

你认为我想要做的甚至是可能的吗?

在我看来,这是因为doSomething()是Base的一部分,但除了使用继承之外,我看不到这样做的方法....

sbi*_*sbi 11

我的目标主要是没有vtable.

如果你想要这个,那么,除非你实现自己的虚拟调度系统(可能不如编译器所做的那样),否则你会遇到模板,即编译时多态.正如名称所说,为了使用它,必须在编译时知道所有内容.如果您需要根据运行时事件(例如,用户输入)做出决策,则需要运行时多态性.

我不禁要问:你为什么要避免vtables?(如果你如此坚定,为什么不用C编程呢?)


Ytt*_*ill 7

你可以通过适当的理论操作来做你想要的,这不是多态,而是统一.大多数人不知道什么是总和类型(歧视联盟)是什么,它是什么,这就是为什么他们一直滥用继承,这是完全不相关的.

联合在C中更受欢迎,例如X-Windows事件消息是基于联合的,因为它们在C++中被破坏了.

工会是 异构数据类型表示为单一类型正确方法,因此名称统一:它将所有组件统一为单一类型.联合总是具有有限的已知数量的组件,使用联合的函数总是使用判别式上的开关来选择正确的处理程序代码.

OOP不能提供统一:它提供子类型.

模板提供了一些完全不同的东西:参数多态.

这三个概念在理论和实践中都是截然不同的.子类型OOP样式被证明是最不实用的,因为它可以代表的是严格限制的,但是这些限制允许动态调度到一组开放的子类型,如果它适用,这非常好于您的问题.

所以现在很清楚,在你的数组中,你需要放的只是所有类的联合,你的问题就会消失.

只有..由于无原则的限制,这些类目前必须是C++中的POD类型.所以最好的解决方案是使用原始C函数的并集,因为C函数指针是POD.

就像是:

enum tag {tag_X, tag_Y};

struct X { int x; };
void px(X a) { cout << a.x; }
struct PX { tag t; X a; void (*p)(X); };

struct Y { double x; };
void py(Y a) { cout << a.x; };
struct PY {tag t; Y a; void (*p)(Y); };

union XY { PX anX; PY anY; };

PX x1 = { tag_X, { 1 }, px };
PY y1 = { tag_Y, { 1.0 }, py };

XY xy1.anPX = x1;
XY xy2.anPy = x2;

XY xys[2] = {xy1, xy1};

xy = xys[0];
switch (xy.anPX.tag) { // hack
  case tag_X: xy.anPX.p (xy.PX.x); break;
  case tag_Y: xy.anPY.p (xy.PY.x); break;
}
Run Code Online (Sandbox Code Playgroud)

如果你觉得这很难看,那你说得对:C和C++已经死了.另一种解决方案是使用标记和指针转换为void*,然后使用标记转换为所需类型:这更容易,但需要堆分配数据,因此存在内存管理问题.另一种选择是Boost变体类型(它可以自动化一些内务处理,但仍然非常难看).

以下是Ocaml中的类似代码:

type xy = X of int | Y of double
let print u =
  match u with 
  | X x -> print_int x 
  | Y x -> print_double x
in 
  print (X 1);
  print (Y 2.0)
Run Code Online (Sandbox Code Playgroud)

在这段代码中,X和Y是上面C代码的标记,它们被称为类型构造函数,因为它们用int或double构造xy类型(resp.).匹配表达式只有一个开关,可以自动选择正确的组件类型和作用域,以确保您不能引用错误的组件(就像在C代码中那样),也没有中断,匹配处理程序不直通,内存管理由垃圾收集器完成.