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

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


person Balaji Sridharan    schedule 24.02.2012    source источник


Ответы (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();
      }

 }

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

person axlan    schedule 24.02.2012
comment
Возможно, это один из лучших примеров использования виртуальных функций. Спасибо! - person Garfield; 05.03.2013
comment
Блестящий ответ, но когда я уже знаю, что мне нужно добавить выбросы для всех этих объектов класса, почему я не могу просто вручную создать объекты как для «автомобиля», так и для «автобуса» и добавить их в обычном режиме? Зачем мне нужен указатель типа базового класса. - person Yankee; 14.09.2017
comment
Предположим, что если мы не используем виртуальную функцию, какая польза от использования указателей базового класса на производный класс? - person Rajesh; 26.09.2017

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

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

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

base *b = new derived;
b->function1();
delete b;
person Mark Ransom    schedule 24.02.2012
comment
Я думаю, что его вопрос был в том, зачем использовать указатели базового класса, а не виртуальные деструкторы. - person Rohit Vipin Mathews; 24.02.2012

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

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

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

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

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

person Rohit Vipin Mathews    schedule 24.02.2012
comment
последняя часть взята из учебника по C++! возьми эту книгу и почитай Полиморфизм - person Rohit Vipin Mathews; 24.02.2012

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

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

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

person RockU    schedule 24.02.2012