Зачем использовать указатели базового класса для производных классов
class base{
.....
virtual void function1();
virtual void function2();
};
class derived::public base{
int function1();
int function2();
};
int main()
{
derived d;
base *b = &d;
int k = b->function1() // Why use this instead of the following line?
int k = d.function1(); // With this, the need for virtual functions is gone, right?
}
Я не инженер CompSci, и я хотел бы это знать. Зачем использовать виртуальные функции, если мы можем избежать указателей базового класса?
Ответы
Ответ 1
Сила полиморфизма на самом деле не очевидна в вашем простом примере, но если вы немного расширите ее, это может стать яснее.
class vehicle{
.....
virtual int getEmission();
}
class car : public vehicle{
int getEmission();
}
class bus : public vehicle{
int getEmission();
}
int main()
{
car a;
car b;
car c;
bus d;
bus e;
vehicle *traffic[]={&a,&b,&c,&d,&e};
int totalEmission=0;
for(int i=0;i<5;i++)
{
totalEmission+=traffic[i]->getEmission();
}
}
Это позволяет вам перебирать список указателей и вызывать разные методы в зависимости от базового типа. В основном это позволяет вам писать код, в котором вам не нужно знать, что такое дочерний тип во время компиляции, но код все равно будет выполнять правильную функцию.
Ответ 2
Вы правы, если у вас есть объект, вам не нужно ссылаться на него с помощью указателя. Вам также не нужен виртуальный деструктор, когда объект будет уничтожен как тип, который он создал.
Утилита приходит, когда вы получаете указатель на объект из другого фрагмента кода, и вы действительно не знаете, что такое самый производный тип. Вы можете иметь два или более производных типа, построенных на одной базе, и иметь функцию, которая возвращает указатель на базовый тип. Виртуальные функции позволят вам использовать указатель, не беспокоясь о том, какой производный тип вы используете, пока не наступит время для уничтожения объекта. Виртуальный деструктор уничтожит объект, не зная, какой производный класс ему соответствует.
Вот простейший пример использования виртуальных функций:
base *b = new derived;
b->function1();
delete b;
Ответ 3
для реализации полиморфизма. Если у вас нет указателя базового класса
указывая на производный объект, вы не можете иметь полиморфизм здесь.
Одна из ключевых особенностей производных классов заключается в том, что указатель на производный класс совместим со шрифтом с указателем на его базовый класс. Полиморфизм - это искусство использования этого простого, но мощная и универсальная функция, которая обеспечивает объектно-ориентированную Методологии в полном объеме.
В С++ существует специальное отношение типа/подтипа, в котором база указатель класса или ссылка могут обращаться к любому из его производных классов подтипы без вмешательства программиста. Эта способность манипулировать более одного типа с указателем или ссылкой на базовый класс говорят о полиморфизме.
Политизм подтипов позволяет нам написать ядро нашего приложения независимо от отдельных типов, которые мы хотим манипулировать. Скорее, мы программировать открытый интерфейс базового класса нашей абстракции через указатели базового класса и ссылки. Во время выполнения тип, на который делается ссылка, разрешен, и соответствующий экземпляр вызывается открытый интерфейс. Разрешение во время выполнения соответствующая функция для вызова называется динамической привязкой (по умолчанию, функции решаются статически во время компиляции). В С++ динамический привязка поддерживается через механизм, называемый виртуальным классом функции. Политизм подтипов через наследование и динамику связывание обеспечивает основу для объектно-ориентированного программирования
Основным преимуществом иерархии наследования является то, что мы можем программировать к публичному интерфейсу абстрактного базового класса, а не к отдельные типы, которые формируют свою иерархию наследования, таким образом защищая наш код от изменений в этой иерархии. Мы определяем eval(), например, как общедоступная виртуальная функция абстрактной базы запросов класс. При написании кода, такого как _rop->eval();
код пользователя защищен от разнообразия и волатильности нашего языка запросов. Это не только позволяет добавлять, или удаление типов, не требуя изменений в пользовательских программах, но освобождает поставщика нового типа запроса от необходимости перекодировать поведение или действия, общие для всех типов в самой иерархии. Это поддерживается двумя особыми характеристиками наследования: полиморфизм и динамическое связывание. Когда мы говорим о полиморфизме в С++, мы прежде всего означает способность указателя или ссылки базового класса для решения любого из его производных классов. Например, если мы определим nonmember function eval() следующим образом://pquery может обращаться к любому из классы, полученные из запроса void eval( const Query *pquery ) { pquery->eval(); }
мы можем ссылаться на него юридически, передавая в адрес объекта любой из четыре типа запросов:
int main()
{
AndQuery aq;
NotQuery notq;
OrQuery *oq = new OrQuery;
NameQuery nq( "Botticelli" ); // ok: each is derived from Query
// compiler converts to base class automatically
eval( &aq );
eval( ¬q );
eval( oq );
eval( &nq );
}
тогда как попытка вызвать eval() с адресом объекта, не полученного из Query приводит к ошибке времени компиляции:
int main()
{ string name("Scooby-Doo" ); // error: string is not derived from Query
eval( &name);
}
Внутри eval() выполняется выполнение pquery- > eval(); должен вызывать подходящая функция eval() виртуального участника, основанная на фактическом классе адреса объекта pquery. В предыдущем примере pckery в свою очередь адресует объект AndQuery, объект NotQuery, объект OrQuery, и объект NameQuery. В каждой точке вызова во время выполнения нашей программы, тип класса, к которому обращается pquery, определяется, и вызывается соответствующий экземпляр eval(). динамический обязательным является механизм, посредством которого это выполняется. В объектно-ориентированной парадигме программист манипулирует неизвестным экземпляром связанного, но бесконечного множества типов. (Набор типы связаны своей иерархией наследования. Теоретически, однако, не ограничивает глубину и ширину этой иерархии.) В С++ это достигается путем манипулирования объектами через базовый класс указатели и ссылки. В объектной парадигме программист манипулирует экземпляром фиксированного единственного типа, который полностью определен в точке компиляции. Хотя полиморфное манипулирование объектом требует, чтобы объект был доступ через либо указатель, либо ссылку, манипулирование указатель или ссылка в С++ сама по себе не обязательно приводит в полиморфизме. Например, рассмотрим
// no polymorphism
int *pi;
// no language-supported polymorphism
void *pvi;
// ok: pquery may address any Query derivation
Query *pquery;
В С++ полиморфизм существует только внутри отдельных иерархий классов. Указатели типа void * могут быть описаны как полиморфные, но они не имеют явного языковая поддержка - то есть они должны управляться программистом через явные приведения и некоторую форму дискриминанта, которая отслеживает от фактического типа, который рассматривается.
Ответ 4
Кажется, вы задали два вопроса (в заголовке и в конце):
-
Зачем использовать указатели базового класса для производных классов?
Это само использование полиморфизма. Это позволяет вам рассматривать объекты равномерно, позволяя вам иметь конкретную реализацию. Если это вас беспокоит, я предполагаю, что вы должны спросить: почему полиморфизм?
-
Зачем использовать виртуальные деструкторы, если мы можем избежать указателей базового класса?
Проблема здесь , вы не можете всегда избегать указателей базового класса, чтобы использовать силу полиморфизма.