这个问题不是关于C++语言本身(即不是关于标准的),而是关于如何调用编译器来实现虚拟函数的替代方案。
实现虚函数的一般方案是使用指向指针表的指针。
class Base {
private:
int m;
public:
virtual metha();
};同样地,在C语言中,类似于
struct Base {
void (**vtable)();
int m;
}第一个成员通常是指向虚拟函数列表等的指针(内存中应用程序无法控制的一块区域)。在大多数情况下,在考虑成员之前,这会花费指针的大小,等等。所以在32位寻址方案中,大约4个字节,等等。如果你在你的应用程序中创建了一个40k多态对象的列表,在任何成员变量之前,这大约是40k x 4字节= 160k字节,等等。我也知道这恰好是C++编译中最快和常见的实现。
我知道这会因为多重继承而变得复杂(特别是在它们里面有虚类,比如菱形结构等)。
另一种方法是将第一个变量作为vptrs表的索引id (在C中等效,如下所示)
struct Base {
char classid; // the classid here is an index into an array of vtables
int m;
}如果应用程序中的类总数少于255个(包括所有可能的模板实例化等),那么char就足以容纳索引,从而减少应用程序中所有多态类的大小(我排除了对齐问题等)。
我的问题是,在GNU C++、LLVM或任何其他编译器中有没有这样做的开关?或者减少多态对象的大小?
编辑:我理解所指出的对齐问题。还有一点,如果这是在64位系统上(假设64位vptr),每个多态对象成员的开销约为8字节,那么vptr的开销是内存的50%。这主要与大量创建的小多态有关,所以我想知道,如果不是整个应用程序,这种方案是否可能至少适用于特定的虚拟对象。
发布于 2012-04-12 22:12:53
您的建议很有趣,但如果可执行文件由多个模块组成,并在这些模块之间传递对象,那么它就不会起作用。假设它们是单独编译的(比如classid ),如果一个模块创建了一个对象并将其传递给另一个模块,而另一个模块调用了一个虚方法-它如何知道DLL引用的是哪个表?您将无法添加另一个moduleid,因为这两个模块在编译时可能不知道对方。所以除非你使用指针,否则我认为这是个死胡同...
发布于 2012-04-12 22:20:51
以下是一些观察结果:
_
_
请不要停止我的尝试,但是使用任何使用可变大小id来派生函数地址的技术都有更多的问题需要解决。
我强烈建议您也在Wikipedia Cola上查看Ian Piumarta's Cola
它实际上采用了一种不同的方法,并以更灵活的方式使用指针来构建继承,或基于原型,或开发人员所需的任何其他机制。
发布于 2012-04-12 22:54:39
不,没有这样的开关。
LLVM/Clang代码库避免了由数万人分配的类中的虚拟表:这在closed层次结构中工作得很好,因为单个enum可以枚举所有可能的类,然后每个类都链接到enum的一个值。closed显然是因为enum。
然后,通过enum上的switch实现虚拟化,并在调用该方法之前进行适当的强制转换。再一次关闭了。必须为每个新类修改switch。
第一种选择是:外部vpointer。
如果您发现自己处于一种经常支付vpointer税的情况下,那就是大多数对象都是已知类型的。然后,您可以将其外部化。
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); }然后,想象一个简单的类:
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); // no v-pointer并且仍然将它们用于多态函数:
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)存储时,您需要克隆。实际上可以同时提供两个接口(和网桥):
Interface <--- 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:
// Implement in .cpp or pay the cost of weak symbols.
static std::vector<VTable> VTables() { static std::vector<VTable> VT; return VT; }
unsigned vpointer;
};然后是一个Derived类:
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中的前几个属性来使用它们,以避免填充...
https://stackoverflow.com/questions/10125140
复制相似问题