Это, наверное, простой вопрос, пожалуйста, потерпите меня, так как я привык к Java …
Допустим, у нас есть интерфейс:
class IDoable {
virtual void do() = 0;
};
Другой класс:
class Base : public IDoable {
//...
virtual void do() { ... }
};
И последний класс, расширяющий наш базовый класс:
class ExtendingBase : public Base {
// some extra functionality
};
Я потерялся в той части, если я хочу составить список IDoable
объекты, которые могут быть Base
объекты или ExtendingBase
объекты. Должен ли я добавить некоторые методы объявления методов в Base
учебный класс? Как работает этот аспект?
У меня есть несколько списков указателей типа IDoable
и если я тогда попытаюсь добавить Base
возражать против этого списка я получаю сообщение об ошибке:
IDoable — это неоднозначная база Base
То же самое, если я попытаюсь добавить ExtendingBase
объект
IDoable является неоднозначной основой ExtendingBase
поскольку do
это чисто виртуальный метод, он должен быть реализован в производном классе. Вы не можете иметь вектор или массив IDoable
объекты, потому что вы не можете создать экземпляр такого объекта. Вы можете иметь вектор или массив указателей или ссылок на объекты.
Если вы создаете ExtendingBase
возразить и вызвать do
функция, она будет вызывать Base
класс первый ExtendingBase
наследует этот метод).
Виртуальный полиморфизм вступает в игру, когда вы вызываете do()
функция из указателя или ссылки базового класса: do()
Будет вызвана функция, соответствующая динамическому типу объекта, на который указывает или на который ссылаются:
class IDoable{
public:
virtual void dof()=0;
virtual ~IDoable() = default;
};class Base:public IDoable{
public:
virtual void dof(){std::cout << "Base";}
virtual ~Base() = default;
};
class ExtendingBase:public Base{
public:
virtual void dof() { std::cout << "ExtendingBase"; }
};
int main()
{
IDoable *ptr = new Base(); // A smart pointer would be a better choice
// but for clarity's sake I'm using bare
// memory allocations here
ptr->dof(); // Walks the virtual table and calls "Base"delete ptr;
ptr = new ExtendingBase();
ptr->dof(); // Walks the virtual table and calls "ExtendingBase"delete ptr;
}
Также обратите внимание на использование виртуальных деструкторов: они работают как обычные виртуальные функции и, таким образом, при вызове delete для базового указателя, чтобы фактически уничтожить нужный тип объекта (т. Е. Вызвать правильный деструктор в иерархии), вам потребуется сделать это виртуальным.
В качестве обозначения: do
является зарезервированным ключевым словом в C ++
В ответ на ваши изменения: если у вас есть вектор или список IDoable
указатели, вы не можете просто добавить производный объект к нему, но вы должны добавить указатель к производному объекту. То есть следующее неправильно:
std::vector<IDoable*> vec;
vec.push_back(Base());
плюс базовый класс остается классом (нет интерфейс концепция в C ++ (как в Java), и вы не должны наследовать от базового класса несколько раз:
class Base:public IDoable{
...
class ExtendingBase:public Base, public IDoable <- nope
...
это может вызвать проблемы только при идентификации базового подобъекта.
Я рекомендую прочитать о проблема страшного алмаза в C ++ (это способ решить базовый класс, появляющийся несколько раз в иерархии наследования … в любом случае, хороший дизайн, возможно, вообще избежит этого).
если я хочу составить список
IDoable
объекты
Вы не можете сделать IDoable
период объекта. Это абстрактный класс, он не может быть создан напрямую, поэтому у вас не может быть его контейнера. То, что вы можете сделать и что вы, вероятно, намереваетесь, это иметь контейнер IDoable*
:
std::vector<IDoable*> objects;
objects.push_back(new Base);
objects.push_back(new ExtendedBase);
Или лучше выразить право собственности в C ++ 11:
std::vector<std::unique_ptr<IDoable>> objects;
Учитывая ваш интерфейс, вы уже можете позвонить do()
на любом из этих объектов, и это будет делать правильные вещи с помощью виртуальной отправки. Хотя есть одна функция-член, которую вы определенно хотите добавить в свой интерфейс:
class IDoable {
public:
virtual ~IDoable() = default; // this one
virtual void do() = 0;
};
Таким образом, когда ты delete
IDoable*
, ты сможешь delete
полный объект, а не только базовый интерфейс.
Вы должны будете реализовать свой do()
функция в Base
, так как функция в классе IDoable
чисто виртуальный.
Если вы решили создать ExtendingBase
объект, do()
Функция будет вести себя так, как она реализована в Base
, если вы не переопределите его, повторно внедрив его в ExtendingBase
,
Первая и самая главная проблема в том, что вы думаете на Java.
слова «интерфейс» и «расширение» очень ориентированы на Java. С ++ так не думает.
например, когда кто-то говорит об «интерфейсе» в контексте C ++, я могу подумать, что он говорит об объявлении класса внутри файла .h (в отличие от реализации, которая находится в файле .cpp)
IDoable — это КЛАСС. период. единственное отличие состоит в том, что он имеет чисто виртуальные функции, которые запрещают инстанциацию. кроме того, что он ведет себя как класс, он может наследоваться, может содержать переменные-члены и все остальное.
вам просто нужно убедиться, что абстрактная функция переопределена в некотором производном классе, чтобы этот класс мог создавать объекты.
так сказал:
//in the stack:
Base base;
ExtendingBase eBase;
base.do();
eBase.do()
//in the heap with IDoable as pointer:
IDoable * base = new Base();
IDoable * ebase = new ExtendingBase ();
base->do();
ebase->do();
Теперь вы можете спросить — как мне активировать функции Base и ExtendingBase? так же, как и в Java, вам нужно привести указатель и только потом вызвать нужную функцию.
Base* realBase = (Base*)base;
realbase->someBaseFunction();
как и многие вещи в C ++, этот код немного опасен. ты можешь использовать dynamic_cast
вместо.
и еще одна вещь — do
это ключевое слово в C ++, оно не может объявить имя функции.
IDoable *pDo1 = new Base();
IDoable *pDo2 = new ExtendingBase();
pDo1->do();
pDo2->do();
delete pDo1;
delete pDo2;