Почему именно это не работает? Являются ли подписи унаследованных функций слегка неправильными, или абстрактный базовый класс применяется «перед» наследованием функций-членов, или это что-то еще? Можно ли убедить это работать без функциональных оболочек?
#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();
}
Я думаю, что downvoters немного суровы с вашей стороны, поскольку ваши аргументы в пользу реализации двух чистых виртуальных функций через отдельные классы имеют некоторую интуитивную привлекательность.
Увы, вы делаете две не связанные вещи одновременно. ProvideFoo
а также ProvideBar
совершенно не связаны с AbsBase
абстрактный класс. Вы также можете подклассы оба из AbsBase
, но тогда каждый из них все равно будет абстрактным классом. В любом случае, ваш текущий Concrete
является абстрактным классом, потому что он происходит по крайней мере от одного класса с чисто виртуальной функцией. Вы не можете создавать объекты из таких классов.
Самый простой способ — отказаться от подклассов 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();
}
Вы также можете создать несколько интерфейсов и несколько конкретных реализаций, что-то вроде этого:
#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();
}
Теперь на бис: вы также можете использовать 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-е решение для вашего кода.
Я не прояснил это в вопросе, но я действительно хотел знать буквально, почему код не компилируется. 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) должен содержать соответствующий отдельный подобъект базового класса этого типа. [ …] «
Здесь ясно, что производный класс и базовый класс остаются разными. Мы не просто наследуем кучу функций. Теперь разница между переопределением члена и доступом к более чем одному члену с одинаковыми именами очевидна. Мы можем наследовать несколько функций с одинаковыми именами и подписями. Мы можем даже ссылаться на отдельные функции, если мы осторожны с областью действия, но неоднозначный поиск имени недопустим.
Чтобы не быть абстрактным классом, унаследованные чистые виртуальные функции должны быть переопределены. Мы действительно наследуем чисто виртуальную функцию, которая не переопределяется, и поэтому у нас есть абстрактный класс. Между прочим, мы также наследуем не чистую виртуальную функцию с идентичной подписью, но это просто несущественные мелочи.