2012-01-24 3 views
1

Учитывая код:Несколько экземпляров подобъекта виртуального базового класса (действительно) - никак?

#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) формулировка, по-видимому, запрещает это, и если это действительно так, то следующий вопрос: не даны ли мы никакие средства точной манипуляции с подобъектной виртуальной надмножеством, и Зачем?

+3

Просто избежать наследования нескольких алмазов. Ваши будущие сопровождающие оценят это. –

+0

@Mark Конечно, я избегу этого в любой практической ситуации. Это принципиальный вопрос :) – mlvljr

+2

Для чистых классов интерфейса (т. Е. Абстрактных классов без элементов данных) виртуальное наследование - это правильная вещь. Не виртуальное наследование может привести к неоднозначности позже, в то время как виртуальное наследование безболезненно в этом случае. – celtschk

ответ

1

После того, как база объявлена ​​виртуальной, все источники этой виртуальной базы сбрасываются в одну базу такого типа, это не позволит вам разделить ее на полпути вверх по иерархии (нет ничего, что можно сказать у ребенка для де-виртуального родителя). Чтобы быть ясным, наследование из другого класса, который практически не унаследовал базу, приведет к другому экземпляру. Вы можете использовать композицию в XYZ, чтобы создать два экземпляра вместо наследования, а затем использовать обычный интерфейс для делегирования по мере необходимости.

+0

_все фактически унаследованные источники, вы имеете в виду :) – mlvljr

1

Как правило, все объединяются виртуальные базовые классы того же типа (не виртуальные базовые классы не объединены с виртуальным). Нет механизма блокирования виртуального базового класса от совместного использования. Вероятно, причина в том, что любой такой механизм потребовал бы достаточно усилий для разработки (а также усилий для составления сценариев компилятора) для очень небольшого выигрыша (вы когда-нибудь попадали в ситуацию, когда вы действительно хотели использовать эту функциональность?)

1

Вы можете добиться своего рода сравнения путем двойной отправки. Это не идеально. Это можно сделать и с меньшим количеством кода, но я просто хотел показать идею, стоящую за ней.

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 
1

Ближайший вещь (не очень):

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) {} 
};