いいえ、そのようなスイッチはありません。
LLVM/Clang コードベースは、数万単位で割り当てられるクラス内の仮想テーブルを回避します。これは、閉じた階層でうまく機能しenum
ますenum
。閉鎖は明らかenum
に.
次に、メソッドを呼び出す前に、 で仮想性を実装し、switch
適切なキャストを行います。enum
もう一度、閉じました。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);
値と参照は、実際には設計上の緊張です。一般に、必要な場合は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
間で 4 バイトのパディングが使用されるリスクがあります。パディングを避けるためBase
に最初のいくつかの属性を慎重に選択することで、それらを使用できます...Derived