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

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, и я хотел бы знать это. Зачем использовать виртуальные функции, если можно избежать указателей базового класса?

4 ответов


сила полиморфизма на самом деле не очевидна в вашем простом примере, но если вы немного расширите его, это может стать более ясным.

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();
      }

 }

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


вы правы, если у вас есть объект, вам не нужно ссылаться на него с помощью указателя. Вам также не нужен виртуальный деструктор, когда объект будет уничтожен как тип, который он был создан.

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

вот самый простой пример использования виртуальных функций:

base *b = new derived;
b->function1();
delete b;

its для реализации полиморфизма. Если у вас нет указателя базового класса указывая на производный объект, вы не можете иметь здесь полиморфизм.

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

в C++, a специальное отношение типа / подтипа, в котором существует база указатель класса или ссылка могут обращаться к любому производному классу подтипы без вмешательства программиста. Эта способность манипулировать более одного типа с указателем или ссылкой на базовый класс говорят о полиморфизме.

полиморфизм подтипа позволяет нам написать ядро нашего приложения независимо от индивидуальных типов, которыми мы хотим манипулировать. Скорее, мы запрограммируйте открытый интерфейс базовый класс нашей абстракции через указатели и ссылки базового класса. Во время выполнения, фактическое тип, на который ссылаются, разрешен и соответствующий экземпляр вызывается открытый интерфейс. Разрешение времени выполнения соответствующая функция для вызова называется динамической привязкой (по умолчанию, функции разрешаются статически во время компиляции). В C++, динамическая привязка поддерживается через механизм, называемый Class virtual функции. Полиморфизм подтипа через наследование и динамика привязка обеспечивает основу для объектного программирования

основным преимуществом иерархии наследования является то, что мы можем программировать к публичному интерфейсу абстрактного базового класса, а не к отдельные типы, которые формируют иерархию наследования, таким образом защита нашего кода от изменений в этой иерархии. Мы определяем функцию eval(), например, как общедоступная виртуальная функция абстрактной базы запросов класс. Путем написания кода, такого как _rop->eval(); пользовательский код защищен от разнообразия и изменчивости нашего языка запросов. Это не только позволяет для добавления, пересмотра, или удаление типов без необходимости изменения пользовательских программ, но освобождает поставщика нового типа запроса от необходимости перекодировать поведение или действия, общие для всех типов в самой иерархии. Это поддерживается двумя особыми характеристиками наследования: полиморфизм и динамическая привязка. Когда мы говорим о полиморфизме в C++, мы прежде всего означает способность указателя или ссылки на базовый класс для обращения к любому из его производных классов. Например, если мы определяем функция, не являющаяся членом 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() с адресом объекта, не производного от запроса результаты в Ошибка времени компиляции:

int main()
 { string name("Scooby-Doo" ); // error: string is not derived from Query 
eval( &name); 
}

в eval (), выполнение pquery - >eval (); должен вызвать соответствующая виртуальная функция-член eval() на основе фактического класса адреса объекта pquery. В предыдущем примере, pquery в свою очередь адресует объект AndQuery, объект NotQuery, объект OrQuery, и объект NameQuery. В каждой точке вызова во время выполнения из нашей программы фактический тип класса, адресованный pquery, решительный, и вызывается соответствующий экземпляр eval (). Активный связывание-это механизм, с помощью которого это достигается. В объектно-ориентированной парадигме программист манипулирует неизвестным экземпляром связанного, но бесконечного набора типов. (Набор типы связаны иерархией наследования. Теоретически, однако, нет предела глубине и широте этой иерархии.) В C++ это достигается за счет манипулирования объектами через базовый класс указатели и ссылки. В объектно-ориентированная парадигма, программист манипулирует экземпляром фиксированного сингулярного типа, который полностью определен в момент компиляции. Хотя полиморфная манипуляция объектом требует, чтобы объект был доступ либо через указатель, либо через ссылку, манипулирование указатель или ссылка в C++ сами по себе не обязательно приводят в полиморфизме. Рассмотрим, например,

// no polymorphism 
  int *pi; 
// no language-supported polymorphism 
  void *pvi; 
// ok: pquery may address any Query derivation
  Query *pquery;

в C++, полиморфизм существовать только внутри отдельных иерархий классов. Указатели типа void* можно описать как полиморфные, но они не являются явными языковая поддержка-то есть ими должен управлять программист через явные приведения и некоторую форму дискриминанта, которая отслеживает фактического типа решается.


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

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

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