Используйте множественное наследование для удовлетворения абстрактного базового класса

Почему именно это не работает? Являются ли подписи унаследованных функций слегка неправильными, или абстрактный базовый класс применяется «перед» наследованием функций-членов, или это что-то еще? Можно ли убедить это работать без функциональных оболочек?

#include <iostream>

struct AbsBase {
virtual void foo() = 0;
virtual void bar() = 0;
};

struct ProvideFoo {
void foo() { std::cout << "foo\n"; }
};

struct ProvideBar {
void bar() { std::cout << "bar\n"; }
};

struct Concrete : public ProvideFoo, public ProvideBar, public AbsBase {
// I guess I could put function wrappers here... sigh...
//void bar() {ProvideBar::bar();}
//void foo() {ProvideFoo::foo();}
};

int main() {
Concrete c;
c.foo();
c.bar();
}

3

Решение

Почему ваш код не компилируется

Я думаю, что downvoters немного суровы с вашей стороны, поскольку ваши аргументы в пользу реализации двух чистых виртуальных функций через отдельные классы имеют некоторую интуитивную привлекательность.

Увы, вы делаете две не связанные вещи одновременно. ProvideFoo а также ProvideBar совершенно не связаны с AbsBase абстрактный класс. Вы также можете подклассы оба из AbsBase, но тогда каждый из них все равно будет абстрактным классом. В любом случае, ваш текущий Concrete является абстрактным классом, потому что он происходит по крайней мере от одного класса с чисто виртуальной функцией. Вы не можете создавать объекты из таких классов.

Исправление вашего кода, часть I

Самый простой способ — отказаться от подклассов AbsBase в целом и подкласс от ProvideFoo а также ProvideBar непосредственно. Конечно, теперь у вас нет virtual функции внутри Concreteпоэтому дальнейшее наследование не может легко переопределить foo а также bar функциональность.

#include <iostream>

struct ProvideFoo {
void foo() { std::cout << "foo\n"; }
};

struct ProvideBar {
void bar() { std::cout << "bar\n"; }
};

struct Concrete : public ProvideFoo, public ProvideBar {};

int main() {
Concrete c;
c.foo();
c.bar();
}

Живой пример I

Исправление вашего кода, часть II

Вы также можете создать несколько интерфейсов и несколько конкретных реализаций, что-то вроде этого:

#include <iostream>

struct AbsFoo {
virtual void foo() = 0;
};

struct AbsBar {
virtual void bar() = 0;
};

struct ProvideFoo: AbsFoo {
void foo() { std::cout << "foo\n"; }
};

struct ProvideBar: AbsBar {
void bar() { std::cout << "bar\n"; }
};

struct Concrete : public ProvideFoo, public ProvideBar {};

int main() {
Concrete c;
c.foo();
c.bar();
}

Живой Пример II

Исправление вашего кода, часть III

Теперь на бис: вы также можете использовать virtual наследование при наследовании ProvideFoo а также ProvideBar от AbsBase используя virtual ключевое слово

#include <iostream>

struct AbsBase {
virtual void foo() = 0;
virtual void bar() = 0;
};

struct ProvideFoo: virtual AbsBase {
void foo() { std::cout << "foo\n"; }
};

struct ProvideBar: virtual AbsBase {
void bar() { std::cout << "bar\n"; }
};

struct Concrete : public ProvideFoo, public ProvideBar {};

int main() {
Concrete c;
c.foo();
c.bar();
}

Это действительно продвинутый C ++ и может стать действительно сложным, если ваши классы также содержат данные членов. Я бы предпочел использовать 2-е решение для вашего кода.

Живой Пример III

7

Другие решения

Я не прояснил это в вопросе, но я действительно хотел знать буквально, почему код не компилируется. TemplateRex дал потрясающий ответ на вопрос, как я его задал.

Тем не менее, вот объяснение того, почему код не компилируется, а также не жалуется на неоднозначное имя члена. Во-первых, вот что-то похожее, что компилируется.

struct A {
virtual void foo() { std::cout << "A::foo()\n"; };
};

struct B {
void foo() { std::cout << "B::foo()\n"; }
};

struct C : public A, public B {};

int main() {
// This is fine.
C c;
// Uncommenting the next line would cause an ambiguous member name lookup and
// invalidate the program.
//c.foo();
}

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

Можно создать действительную программу с безкомментированной функцией c.foo (), добавив еще больше определений функции foo ().

// Name lookup never proceeds to the base classes if it succeeds locally. 10.2.4
struct C : public A, public B {
void foo() { std::cout << "C::foo()\n"; }
};

Изменение A на абстрактный класс путем превращения A :: foo () в чисто виртуальную функцию предотвращает компиляцию.

struct A {
virtual void foo() = 0;
};

struct B {
void foo() { std::cout << "C::foo()\n"; }
};

struct C : public A, public B {};

int main() {
// This is illegal.
C c;
// The next line is irrelevant.
//c.foo();
}

Ошибка компилятора указывает на то, что структура C является абстрактной. Зачем? Давайте начнем с того, что значит быть абстрактным классом.

«10.4 Абстрактные занятия

2 Абстрактный класс — это класс, который может использоваться только как базовый класс какого-либо другого класса; никакие объекты абстрактного класса не могут быть созданы, кроме как подобъекты класса, производного от него. Класс является абстрактным, если он имеет хотя бы одну чисто виртуальную функцию.
«

Очевидно, C имеет по крайней мере одну чисто виртуальную функцию и поэтому является абстрактным классом, и мы можем наследовать только от него. Прежде чем перейти к тому, почему C имеет чисто виртуальную функцию, мы уже можем ответить на часть вопроса. C — абстрактный класс, и мы попытались создать экземпляр, это незаконно. Просто создание объект незаконен. Не имеет значения, если вы никогда не пытаетесь получить доступ к чисто виртуальной функции.

Так почему же в C есть чисто виртуальная функция? Это должен быть A :: foo (). Что случилось с B :: foo ()? A :: foo () каким-то образом имеет приоритет?

Во-первых, мы обычно говорим, что производные классы «имеют» функции, которые они наследуют, но это затемняет то, что происходит на самом деле.

«10.1.4 […] Для каждого отдельного вхождения не виртуального базового класса в решетке классов самого производного класса наиболее производный объект (1.8) должен содержать соответствующий отдельный подобъект базового класса этого типа. [ …] «

Здесь ясно, что производный класс и базовый класс остаются разными. Мы не просто наследуем кучу функций. Теперь разница между переопределением члена и доступом к более чем одному члену с одинаковыми именами очевидна. Мы можем наследовать несколько функций с одинаковыми именами и подписями. Мы можем даже ссылаться на отдельные функции, если мы осторожны с областью действия, но неоднозначный поиск имени недопустим.

Чтобы не быть абстрактным классом, унаследованные чистые виртуальные функции должны быть переопределены. Мы действительно наследуем чисто виртуальную функцию, которая не переопределяется, и поэтому у нас есть абстрактный класс. Между прочим, мы также наследуем не чистую виртуальную функцию с идентичной подписью, но это просто несущественные мелочи.

0

По вопросам рекламы [email protected]