Множественное наследование: 2Classes1Method

Я только что пробовал этот фрагмент кода:

struct FaceOfPast
{
    virtual void Smile() = 0;
};

struct FaceOfFuture
{
    virtual void Smile() = 0;
};

struct Janus : public FaceOfPast, public FaceOfFuture
{
    virtual void Smile() {printf(":) ");}
};

...

void main()
{
    Janus* j = new Janus();
    FaceOfFuture* future = j;
    FaceOfPast* past = j;

    future->Smile();
    past->Smile();

    delete j;
}

Он работает по назначению (выводит два смайлика), но я не думаю, что он должен даже компилироваться, а переопределение Smile() в Janus неоднозначно.

Как (и почему) это работает?

Ответ 1

Нет никакой двусмысленности, потому что вы вызываете Smile() на указатели на FaceOfFuture и FaceOfPast, которые объявляют только один метод Smile().

Поскольку вызов метода на указателе базового класса не может привести к двусмысленности, давайте рассмотрим ситуации, когда вы вызываете метод непосредственно по указателю дочернего класса:

Janus* j = new Janus();
j->Smile();

Производный класс, помимо переопределения, также скрывает объявление базовых классов Smile(). У вас была бы двусмысленность, если бы вы не переопределили метод в производном классе:

Следующие компиляции:

struct FaceOfPast
{
    virtual void Smile() {printf(":) ");}
};
struct FaceOfFuture
{
    virtual void Smile() {printf(":) ");}
};
struct Janus : public FaceOfPast, public FaceOfFuture
{
   virtual void Smile() {printf(":) ");}
};
int main()
{
   Janus* j = new Janus();
   j->Smile();
}

Хотя вы вызываете Smile в Janus, объявления базового класса скрыты.

Нельзя:

struct FaceOfPast
{
    virtual void Smile() {printf(":) ");}
};

struct FaceOfFuture
{
    virtual void Smile() {printf(":) ");}
};

struct Janus : public FaceOfPast, public FaceOfFuture
{
};

int main()
{
   Janus* j = new Janus();
   j->Smile();
}

Из-за двусмысленности.

Ответ 2

Согласно стандарту С++ (10.3.2):

Если виртуальная функция-член vf объявлена ​​в классе Base и в классе Derived, полученном прямо или косвенно из Base, объявлена ​​функция члена vf с тем же именем, списком параметров, cv-qualification и ref-qualifier (или отсутствием такого же), что и Base:: vf то Derived:: vf [...] переопределяет Base:: vf.

Как правило, для множественного наследования нет никакого специального лечения, поэтому он, скорее всего, применим и здесь: void Janus::Smile() переопределяет оба метода без какой-либо двусмысленности, просто потому, что он имеет то же имя и подпись, что и методы базового класса.

Ответ 3

Janus* j = new Janus();
FaceOfFuture* future = j;
FaceOfPast* past = j;

Этот раздел кода переходит к базовому классу. Поэтому, когда вы делаете следующее

future->Smile();
past->Smile();

Это фактический указатель на FaceofPast и FaceOfFuture.