Указатель базового класса против унаследованного указателя класса? - PullRequest
5 голосов
/ 23 апреля 2010

Предположим, у меня есть класс Dog, который наследуется от класса Animal. В чем разница между этими двумя строками кода?

    Animal *a = new Dog();
    Dog *d = new Dog();

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

Ответы [ 8 ]

11 голосов
/ 23 апреля 2010

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

  • Вы не можете передать a функции, ожидающей Dog*.
  • Вы не можете сделать a->fetchStick(), где fetchStick является функцией-членом Dog, но не Animal.
  • Dog *d2 = dynamic_cast<Dog*>(d), вероятно, просто копия указателя на вашем компиляторе. Dog *d3 = dynamic_cast<Dog*>(a), вероятно, нет (я размышляю здесь, я не буду беспокоиться о проверке на любом компиляторе. Дело в том, что компилятор, вероятно, делает различные предположения о a и d при преобразовании кода).
  • и т.д.

Вы можете вызывать виртуальные функции (то есть определенный полиморфный интерфейс) Animal одинаково через любую из них, с одинаковым эффектом. Предполагая, что Dog не спрятал их, в любом случае (хорошая точка зрения, ДжаредПар).

Для не виртуальных функций, которые определены в Animal, а также определены (перегружены) в Dog, вызов этой функции через a отличается от вызова ее через d.

4 голосов
/ 23 апреля 2010

Ответ на этот вопрос гигантский: зависит

Существует множество способов, которыми тип указателя может стать важным. C ++ - очень сложный язык, и один из способов, которым он обнаруживается, - наследование.

Давайте возьмем короткий пример, чтобы продемонстрировать один из многих способов, которыми это может иметь значение.

class Animal {
public:
  virtual void MakeSound(const char* pNoise) { ... }
  virtual void MakeSound() { ... }
};

class Dog : public Animal {
public:
  virtual void MakeSound() {... }
};

int main() {
  Animal* a = new Dog();
  Dog* d = new Dog();
  a->MakeSound("bark");
  d->MakeSound("bark"); // Does not compile
  return 0;
}

Причина, по которой причудой является то, как C ++ выполняет поиск имен. Вкратце: при поиске метода для вызова C ++ будет проходить иерархия типов в поисках первого типа, у которого есть метод с соответствующим именем. Затем он будет искать правильную перегрузку из методов с этим именем, объявленным для этого типа. Поскольку Dog объявляет только метод MakeSound без параметров, перегрузка не совпадает, и он не может быть скомпилирован.

2 голосов
/ 23 апреля 2010

Различие важно, когда вы вызываете виртуальную функцию с помощью указателя. Допустим, в Animal и Dog есть функции с именем do_stuff ().

  1. Если Animal :: do_stuff () объявлен виртуальным, вызов do_stuff () для указателя Animal вызовет Dog :: do_stuff ().

  2. Если Animal :: do_stuff () не объявлен виртуальным, вызов do_stuff () для указателя Animal вызовет Animal :: do_stuff ().

Вот полная рабочая программа для демонстрации:

#include <iostream>

class Animal {
public:
        void do_stuff() { std::cout << "Animal::do_stuff\n"; }
        virtual void virt_stuff() { std::cout << "Animal::virt_stuff\n"; }
};

class Dog : public Animal {
public:
        void do_stuff() { std::cout << "Dog::do_stuff\n"; }
        void virt_stuff() { std::cout << "Dog::virt_stuff\n"; }
};

int main(int argc, char *argv[])
{
        Animal *a = new Dog();
        Dog *b = new Dog();

        a->do_stuff();
        b->do_stuff();
        a->virt_stuff();
        b->virt_stuff();
}

Выход:

Animal::do_stuff
Dog::do_stuff
Dog::virt_stuff
Dog::virt_stuff

Это только один пример. Другие ответы перечисляют другие важные различия.

2 голосов
/ 23 апреля 2010

Первая строка позволяет вам вызывать только членов класса Animal на:

Animal *a = new Dog();
a->eat(); // assuming all Animal can eat(), here we will call Dog::eat() implementation.
a->bark(); // COMPILATION ERROR : bark() is not a member of Animal! Even if it's available in Dog, here we manipulate an Animal.

Хотя (как указано другими), в этом случае как a все еще является животным, вы не можете предоставить a в качестве параметра функции, запрашивающей более конкретный дочерний класс, который является Dog:

void toy( Dog* dog );

toy( a ); // COMPILATION ERROR : we want a Dog!

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

Dog *a = new Dog();
a->bark(); // works, but only because we're manipulating a Dog

Так что используйте базовый класс в качестве «универсального» интерфейса вашей иерархии классов (позволяя вам заставить всех своих животных есть () без забот о том, как).

1 голос
/ 23 апреля 2010

Нет, они не одинаковы.

Указатель Dog не такой полиморфный, как Animal. Все, на что он может указывать во время выполнения - это Собака или подкласс Собаки. Если нет подклассов Dog, то тип времени выполнения Dog и типы времени компиляции совпадают.

Указатель Animal может относиться к любому подклассу Animal: Dog, Cat, Wildebeast и т.д.

0 голосов
/ 23 апреля 2010

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

Ваш код действительно создал 2 объекта Dog в куче. Что означает, что данные о собаке. Этот объект имеет размер суммы всех членов данных Dog + Animal + указатель vtable.

Понтеры a и d (lvalues) отличаются с точки зрения интерфейса. Что определяет, как вы можете обращаться с ними по коду. Так что, хотя Animal * a действительно Dog, вы не можете получить доступ к a-> Bark (), даже если существует Dog :: Bark (). d-> Bark () работал бы нормально.

Добавление vtable обратно в изображение, предполагая, что интерфейс Animal имеет Animal :: Move универсальный Move (), и что Dog действительно перезаписывается с помощью Dog :: Move () {как собака}.

Даже если бы у вас был Animal a * и вы выполняли a-> Move () благодаря vtable, вы бы на самом деле Move () {как собака}. Это происходит потому, что Animal :: Move () был (виртуальным) указателем на функцию, перенаправленную на Dog's :: Move () при создании Dog ().

0 голосов
/ 23 апреля 2010

Разница важна, когда вы пытаетесь вызвать методы Dog, которые не являются методами Animal.В первом случае (указатель на животное) сначала нужно навести указатель на собаку.Другое отличие, если вы перегружаете не виртуальный метод.Затем будет вызван либо Animal :: non_virtual_method () (указатель на Animal), либо Dog :: non_virtual_method (указатель на Dog).

0 голосов
/ 23 апреля 2010

Это не имеет реального значения во время выполнения, так как два экземпляра одинаковы. Единственная разница - во время компиляции, где вы можете вызвать, например, d-> bark (), но не a-> bark (), даже если a фактически содержит собаку. Компилятор считает переменную животным и только этим.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...