Несколько экземпляров подобъекта виртуального базового класса (действительно) - нет?
Учитывая код:
#include <cassert>
struct X {};
struct Y1: virtual X {};
struct Y2: virtual X {};
struct Y3: virtual X {};
struct Y4: virtual X {};
struct Z1: Y1, Y2 {};
struct Z2: Y3, Y4 {};
struct XYZ: Z1, Z2 {};
int main() {
XYZ xyz;
X *x1 = static_cast<Y1*>(&xyz);
X *x2 = static_cast<Y2*>(&xyz);
X *x3 = static_cast<Y3*>(&xyz);
X *x4 = static_cast<Y4*>(&xyz);
assert( x1 == x2 ); //first pair, ok
assert( x2 == x3 ); //can we make this one fail?
assert( x3 == x4 ); //second pair, ok
return 0;
}
Можем ли мы сделать второй утверждение провал?
Другими словами, это тот случай, когда у нас есть граф наследования из двух алмазов, и мы хотели бы иметь отдельные подобъекты для вершин обоих алмазов в самом производном объекте.
Стандартная формулировка (2003, 10.1.4), по-видимому, запрещает это, и если это действительно так, то последующий вопрос заключается в следующем: не дано ли нам никаких средств для точного виртуального манипулирования вложенными структурами подобъектов и почему?
4 ответа
Как правило, все виртуальные базовые классы одного типа объединяются (однако не виртуальные базовые классы не объединяются с виртуальными). Там нет механизма, чтобы заблокировать виртуальный базовый класс от общего доступа. Вероятно, причина в том, что любой такой механизм потребовал бы довольно больших усилий для разработки (а также усилий для реализации создателей компиляторов) с очень небольшим выигрышем (вы когда-нибудь оказывались в ситуации, когда вы действительно желали получить такую функциональность?)
Вы можете добиться какого-то сравнения с помощью двойной отправки. Это не идеально. Это можно сделать и с меньшим количеством кода, но я просто хотел показать идею, стоящую за этим.
class BaseX {
bool Equals(BaseX* potentialBaseX) {
if(potentialBaseX) {
return potentialBaseX->TestEquals(this);
}
// handles null
return false;
}
// OK: x to x
virtual bool TestEquals(BaseX* baseX) { return true; }
virtual bool TestEquals(DerivedY1* derivedY) { return false; }
virtual bool TestEquals(DerivedY2* derivedY) { return false; }
virtual bool TestEquals(DerivedY3* derivedY) { return false; }
virtual bool TestEquals(DerivedY4* derivedY) { return false; }
};
class DerivedY1 {
// OK: y1 to y1, y1 to y2
virtual bool TestEquals(BaseX* baseX) { return false; }
virtual bool TestEquals(DerivedY1* derivedY) { return true; }
virtual bool TestEquals(DerivedY2* derivedY) { return true; }
virtual bool TestEquals(DerivedY3* derivedY) { return false; }
virtual bool TestEquals(DerivedY4* derivedY) { return false; }
};
class DerivedY2 {
// OK: y2 to y2, y2 to y1
virtual bool TestEquals(BaseX* baseX) { return false; }
virtual bool TestEquals(DerivedY1* derivedY) { return true; }
virtual bool TestEquals(DerivedY2* derivedY) { return true; }
virtual bool TestEquals(DerivedY3* derivedY) { return false; }
virtual bool TestEquals(DerivedY4* derivedY) { return false; }
};
class DerivedY3 {
// OK: y3 to y3, y3 to y4
virtual bool TestEquals(BaseX* baseX) { return false; }
virtual bool TestEquals(DerivedY1* derivedY) { return false; }
virtual bool TestEquals(DerivedY2* derivedY) { return false; }
virtual bool TestEquals(DerivedY3* derivedY) { return true; }
virtual bool TestEquals(DerivedY4* derivedY) { return true; }
};
class DerivedY4 {
// OK: y4 to y4, y4 to y3
virtual bool TestEquals(BaseX* baseX) { return false; }
virtual bool TestEquals(DerivedY1* derivedY) { return false; }
virtual bool TestEquals(DerivedY2* derivedY) { return false; }
virtual bool TestEquals(DerivedY3* derivedY) { return true; }
virtual bool TestEquals(DerivedY4* derivedY) { return true; }
};
//Using your example:
assert( x1.Equals(x2) ); //first pair, ok
assert( x2.Equals(x3) ); //can we make this one fail?
assert( x3.Equals(x4) ); //second pair, ok
Ближайшая вещь (не красивая):
struct XYZ1;
struct XYZ2;
struct XYZ1 : Z1 {
XYZ2 &self;
XYZ1 (XYZ2 &self) : self(self) {}
};
struct XYZ2 : Z2 {
XYZ1 &self;
XYZ2 (XYZ1 &self) : self(self) {}
};
struct XYZ {
XYZ1 m1;
XYZ2 m2;
XYZ() : m1(m2), m2(m1) {}
};
Как только база объявляется виртуальной, все источники этой виртуальной базы объединяются в одну базу этого типа, это не позволит вам разделить ее на половину иерархии (нет ничего, что вы могли бы сказать ребенку, чтобы де-виртуализировать родителя).). Просто чтобы прояснить, наследование от другого класса, который не фактически наследовал базу, приведет к другому экземпляру. Вы можете использовать композицию в XYZ
создать два экземпляра вместо наследования, а затем использовать нормальный интерфейс для делегирования в зависимости от ситуации.