C++ Правильно ли я понимаю полиморфизм?

Bar и Box являются производными классами от Foo, и у Foo есть виртуальная функция F(), а у Bar и Box есть функция F(). Насколько я понимаю, полиморфизм правильно позволяет Bar.F() вместо Box.F() или Box.F() вместо Bar.F() переопределять Foo.F() с помощью некоторой процедуры времени выполнения, не зная, является ли ваш объект Бар или Коробка. Это что-то вроде этого:

Foo * boxxy = new Box;
boxxy->F();

Последняя строка вызовет правильный F() (в данном случае Box.F()) независимо от того, является ли boxxy Box, Bar или Foo (в этом случае вызывается реализация виртуального Foo.F()). ).

Я правильно понимаю? А что изменится, если boxxy будет указателем Box? А что произойдет, если производный класс не имеет переопределения для F()? Наконец, чтобы избежать реализации функции для базового класса, но при этом разрешить полиморфизм, вы просто пишете пустое тело функции и объявляете его виртуальным? Спасибо.


person Michael Zimmerman    schedule 14.04.2011    source источник


Ответы (5)


Почти верно - рассмотрим это дерево наследования:

      Foo
     /   \
   Bar   Box

Если вы сейчас сделаете такой указатель:

Bar* barry = new Box();

Вы получите приятную ошибку компилятора, поскольку Box нельзя преобразовать в Bar. :)
Так что это только Foo<->Bar и Foo<->Box, никогда Bar<->Box. Затем, когда boxxy является указателем Box, он будет вызывать только функцию Box::F, если она предоставлена.
И, наконец, чтобы заставить подклассы реализовать определенную функцию, вы объявляете ее pure virtual, например так:

virtual void Func() = 0;
//   note this --- ^^^^

Теперь подклассы (в данном случае Bar и Box) должны реализовывать Func, иначе они не смогут скомпилироваться.

person Xeo    schedule 14.04.2011
comment
Большое спасибо. Просто из любопытства вы можете использовать NULL вместо 0, верно? - person Michael Zimmerman; 14.04.2011
comment
@Brandon: Нет, не при объявлении чистой виртуальной функции. Точный токен, который необходим, это =0, и ничего больше. Кроме того, я бы посоветовал вам использовать 0 вместо NULL, а когда появится C++0x, использовать nullptr для указателя. :) - person Xeo; 14.04.2011

Да, правильный F() будет вызываться в зависимости от типа объекта, который вы создали с помощью указателя Foo.

Если бы boxxy был указателем Box, вы могли бы вызвать только его F() или F() одного из его производных классов, если вы не выполнили dynamic_cast для его родительского класса, а затем вызвали F().

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

class Foo
{
public:
    virtual void F() = 0; //notice the = 0, makes this function pure virtual.

};
person Tony The Lion    schedule 14.04.2011

А что изменится, если boxxy будет указателем Box?

Это позволит получить доступ к методам Box, не унаследованным от Foo. Указатель Box не может указывать на объекты Bar, поскольку Bar не является производным от Box.

А что произойдет, если производный класс не имеет переопределения для F()?

Он унаследует реализацию F() от базового класса.

Наконец, чтобы избежать реализации функции для базового класса, но при этом разрешить полиморфизм, вы просто пишете пустое тело функции и объявляете его виртуальным?

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

person pic11    schedule 14.04.2011

Если вы объявите Foo следующим образом

class Foo
{
private:
  Foo() {};
public:
  void func() const { std::cout << "Calling Foo::func()" << std::endl; }
};

и бар вот такой

class Bar : public Foo
{
private:
  Bar() {};
public:
  void func() const { std::cout << "Calling Bar::func()" << std::endl; }
};

тогда

Foo* bar = new Bar();
bar->func();

вызовет Foo::func().

Если вы объявите Foo следующим образом

class Foo
{
private:
  Foo() {};
public:
  virtual void func() const { std::cout << "Calling Foo::func()" << std::endl; } // NOTICE THE virtual KEYWORD
};

тогда

Foo* bar = new Bar();
bar->func();

вызовет Bar::func().

person Ken    schedule 14.04.2011

  • Я правильно понимаю? Да, если функция была объявлена ​​как виртуальная.
  • А что изменится, если boxxy будет указателем Box? Зависит от того, является ли функция виртуальной или нет. Виртуальная функция всегда будет вызывать правильную производную функцию; невиртуальная функция будет вызывать версию на основе типа указателя.
  • А что произойдет, если производный класс не имеет переопределения для F()? Он будет использовать определение базового класса.
  • Наконец, чтобы избежать реализации функции для базового класса, но при этом разрешить полиморфизм, вы просто пишете пустое тело функции и объявляете его виртуальным? Вы также можете объявить его чисто виртуальным: virtual void F() = 0. Любой класс, который намеревается быть воплощенным в объект, значительно переопределяет эту функцию и дает ей правильную реализацию.
person Mark Ransom    schedule 14.04.2011