Зачем использовать указатели базового класса для производных классов

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( &notq ); 
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

Кажется, вы задали два вопроса (в заголовке и в конце):

  • Зачем использовать указатели базового класса для производных классов? Это само использование полиморфизма. Это позволяет вам рассматривать объекты равномерно, позволяя вам иметь конкретную реализацию. Если это вас беспокоит, я предполагаю, что вы должны спросить: почему полиморфизм?

  • Зачем использовать виртуальные деструкторы, если мы можем избежать указателей базового класса? Проблема здесь , вы не можете всегда избегать указателей базового класса, чтобы использовать силу полиморфизма.