实现vptr的替代方案? [英] Alternative schemes for implementing vptr?

查看:142
本文介绍了实现vptr的替代方案?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个问题不是关于C ++语言本身(即不是关于标准),而是关于如何调用编译器来实现虚拟函数的替代方案。



用于实现虚拟函数的一般方案是使用指向指针表的指针。

  class Base {
private:
int m;
public:
virtual metha();
};

等效地说,C将是

  struct Base {
void(** vtable)();
int m;
}

第一个成员通常是指向虚函数列表的指针等。 (应用程序没有控制的存储器中的一块区域)。并且在大多数情况下,这发生在考虑成员之前花费指针的大小等。因此在32位寻址方案大约4个字节等等。如果你在你的应用程序中创建了一个40k多态对象的列表,这是大约40k x 4个字节=任何成员变量之前的160k字节等等。我也知道这是C ++编译中最快,最常见的实现。



我知道这是复杂的多继承(特别是在其中的虚拟类,即菱形结构等)。



另一种方法是将第一个变量作为索引id vptrs表(等效地在C中如下)

  struct Base {
char classid; //这里的classid是一个到vtables数组的索引
int m;
}

如果应用程序中的类总数小于255可能的模板实例化等),那么char足以保存索引,从而减小应用程序中所有多态类的大小(我排除了对齐问题等)。



我的问题是,是否有任何开关在GNU C + +,LLVM或任何其他编译器这样做?或减少多态对象的大小?



编辑:我理解对齐问题指出。还有一点,如果这是在64位系统上(假设64位vptr),每个多态对象成员花费大约8字节,则vptr的成本是内存的50%。这主要涉及在大量创建的小多态,所以我想知道如果不是整个应用程序,这个方案是否可能至少具体的虚拟对象。

解决方案

不,没有这样的切换。



LLVM / Clang代码库避免了几万个分配的类中的虚拟表:在关闭层次中,因为单个枚举可以枚举所有可能的类,然后每个类链接到 enum 关闭显然是因为枚举



枚举上的开关,并在调用该方法之前进行适当的转换。再次,已关闭开关必须针对每个新类别进行修改。






第一个选择:外部vpointer。



如果你发现自己处于vpointer税的支付方式太频繁的情况,那就是大多数对象是已知类型。然后你可以外部化它。

  class Interface {
public:
virtual〜Interface

virtual Interface * clone()const = 0; //可能是值得的

virtual void updateCount(int)= 0;

protected:
接口(interface const&){}
接口& operator =(Interface const&){return * this; }
};

template< typename T>
class InterfaceBridge:public Interface {
public:
InterfaceBridge(T& t):t(t){}

virtual InterfaceBridge * clone new InterfaceBridge(* this); }

virtual void updateCount(int i){t.updateCount(i); }

私人:
T& t; //值或引用?选择...
};

template< typename T>
InterfaceBridge< T>接口(T& t){return InterfaceBridge< T& }

然后,想象一个简单的类:

  class Counter {
public:
int getCount()const {return c; }
void updateCount(int i){c = i; }
private:
int c;
};

您可以将对象存储在数组中:

  static Counter array [5]; 

assert(sizeof(array)== sizeof(int)* 5); //无v指针

仍然使用它们与多态函数:

  void five(Interface& i){i.updateCount(5); } 

InterfaceBridge< Counter> ib(array [3]); // create * one * v-pointer
five(ib);

assert(array [3] .getCount()== 5);

价值vs参考实际上是一种设计张力。一般来说,如果你需要 clone 你需要按值存储,你需要克隆当你存储基类( boost :: ptr_vector )。可以实际提供两个接口(和网桥):

 接口< --- ClonableInterface 
| |
InterfaceB ClonableInterfaceB

这只是额外输入。






另一个解决方案,更多的涉及。



开关可以通过跳转表来实现。这样的表可以完美地在运行时在 std :: vector 中创建,例如:

  class Base {
public:
〜Base(){VTables()[vpointer] .dispose(* this); }

void updateCount(int i){
VTables()[vpointer] .updateCount(* this,i);
}

protected:
struct VTable {
typedef void(* Dispose)(Base&);
typedef void(* UpdateCount)(Base& int);

Dispose dispose;
UpdateCount updateCount;
};

static void NoDispose(Base&){}

static unsigned RegisterTable(VTable t){
std :: vector< VTable> v = VTables();
v.push_back(t)
return v.size() - 1;
}

explicit Base(unsigned id):vpointer(id){
assert(id< VTables.size());
}

private:
//实现在.cpp或支付弱符号的成本。
static std :: vector< VTable> VTables(){static std :: vector< VTable> VT; return VT; }

unsigned vpointer;
};

然后, Derived / p>

  class Derived:public Base {
public:
Derived():Base(GetID }

private:
static void UpdateCount(Base& b,int i){
static_cast< Derived&>(b).count = i;
}

static unsigned GetID(){
static unsigned ID = RegisterTable(VTable({& NoDispose,& UpdateCount}))
return ID;
}

unsigned count;
};

好了,现在你会意识到编译器为你做了多少一些开销的成本。



哦,并且因为对齐,一旦 Derived 类引入了一个指针,则存在在 Base 和下一个属性之间使用4个字节的填充的风险。您可以通过谨慎选择 Derived 中的前几个属性来使用它们,以避免填充...


This question is not about the C++ language itself(ie not about the Standard) but about how to call a compiler to implement alternative schemes for virtual function.

The general scheme for implementing virtual functions is using a pointer to a table of pointers.

class Base {
     private:
        int m;
     public:
        virtual metha();
};

equivalently in say C would be something like

struct Base {
    void (**vtable)();
    int m;
}

the first member is usually a pointer to a list of virtual functions, etc. (a piece of area in the memory which the application has no control of). And in most case this happens to cost the size of a pointer before considering the members, etc. So in a 32bit addressing scheme around 4 bytes, etc. If you created a list of 40k polymorphic objects in your applications, this is around 40k x 4 bytes = 160k bytes before any member variables, etc. I also know this happens to be the fastest and common implementation among C++ compiles.

I know this is complicated by multiple inheritance (especially with virtual classes in them, ie diamond struct, etc).

An alternative way to do the same is to have the first variable as a index id to a table of vptrs(equivalently in C as below)

struct Base {
    char    classid;     // the classid here is an index into an array of vtables
    int     m;
}

If the total number of classes in an application is less than 255(including all possible template instantiations, etc), then a char is good enough to hold an index thereby reducing the size of all polymorphic classes in the application(I am excluding alignment issues, etc).

My questions is, is there any switch in GNU C++, LLVM, or any other compiler to do this?? or reduce the size of polymorphic objects?

Edit: I understand about the alignment issues pointed out. Also a further point, if this was on a 64bit system(assuming 64bit vptr) with each polymorphic object members costing around 8 bytes, then the cost of vptr is 50% of the memory. This mostly relates to small polymorphics created in mass, so I am wondering if this scheme is possible for at least specific virtual objects if not the whole application.

解决方案

No, there is no such switch.

The LLVM/Clang codebase avoids virtual tables in classes that are allocated by the tens of thousands: this work well in a closed hierachy, because a single enum can enumerate all possible classes and then each class is linked to a value of the enum. The closed is obviously because of the enum.

Then, virtuality is implemented by a switch on the enum, and appropriate casting before calling the method. Once again, closed. The switch has to be modified for each new class.


A first alternative: external vpointer.

If you find yourself in a situation where the vpointer tax is paid way too often, that is most of the objects are of known type. Then you can externalize it.

class Interface {
public:
  virtual ~Interface() {}

  virtual Interface* clone() const = 0; // might be worth it

  virtual void updateCount(int) = 0;

protected:
  Interface(Interface const&) {}
  Interface& operator=(Interface const&) { return *this; }
};

template <typename T>
class InterfaceBridge: public Interface {
public:
  InterfaceBridge(T& t): t(t) {}

  virtual InterfaceBridge* clone() const { return new InterfaceBridge(*this); }

  virtual void updateCount(int i) { t.updateCount(i); }

private:
  T& t; // value or reference ? Choose...
};

template <typename T>
InterfaceBridge<T> interface(T& t) { return InterfaceBridge<T>(t); }

Then, imagining a simple class:

class Counter {
public:
  int getCount() const { return c; }
  void updateCount(int i) { c = i; }
private:
  int c;
};

You can store the objects in an array:

static Counter array[5];

assert(sizeof(array) == sizeof(int)*5); // no v-pointer

And still use them with polymorphic functions:

void five(Interface& i) { i.updateCount(5); }

InterfaceBridge<Counter> ib(array[3]); // create *one* v-pointer
five(ib);

assert(array[3].getCount() == 5);

The value vs reference is actually a design tension. In general, if you need to clone you need to store by value, and you need to clone when you store by base class (boost::ptr_vector for example). It is possible to actually provide both interfaces (and bridges):

Interface <--- ClonableInterface
  |                 |
InterfaceB     ClonableInterfaceB

It's just extra typing.


Another solution, much more involved.

A switch is implementable by a jump table. Such a table could perfectly be created at runtime, in a std::vector for example:

class Base {
public:
  ~Base() { VTables()[vpointer].dispose(*this); }

  void updateCount(int i) {
    VTables()[vpointer].updateCount(*this, i);
  }

protected:
  struct VTable {
    typedef void (*Dispose)(Base&);
    typedef void (*UpdateCount)(Base&, int);

    Dispose dispose;
    UpdateCount updateCount;
  };

  static void NoDispose(Base&) {}

  static unsigned RegisterTable(VTable t) {
    std::vector<VTable>& v = VTables();
    v.push_back(t);
    return v.size() - 1;
  }

  explicit Base(unsigned id): vpointer(id) {
    assert(id < VTables.size());
  }

private:
  // Implement in .cpp or pay the cost of weak symbols.
  static std::vector<VTable> VTables() { static std::vector<VTable> VT; return VT; }

  unsigned vpointer;
};

And then, a Derived class:

class Derived: public Base {
public:
  Derived(): Base(GetID()) {}

private:
  static void UpdateCount(Base& b, int i) {
    static_cast<Derived&>(b).count = i;
  }

  static unsigned GetID() {
    static unsigned ID = RegisterTable(VTable({&NoDispose, &UpdateCount}));
    return ID;
  }

  unsigned count;
};

Well, now you'll realize how great it is that the compiler does it for you, even at the cost of some overhead.

Oh, and because of alignment, as soon as a Derived class introduces a pointer, there is a risk that 4 bytes of padding are used between Base and the next attribute. You can use them by careful selecting the first few attributes in Derived to avoid padding...

这篇关于实现vptr的替代方案?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆