Указатель базового класса по сравнению с наследованным указателем класса?

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

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

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

5
задан Steve Jessop 23 April 2010 в 17:52
поделиться

8 ответов

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

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

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

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

11
ответ дан 18 December 2019 в 08:27
поделиться

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

Существует множество способов, при которых тип указателя может стать важным. 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 без параметров, перегрузка не совпадает, и он не может быть скомпилирован.

4
ответ дан 18 December 2019 в 08:27
поделиться

Нет, это не одно и то же.

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

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

1
ответ дан 18 December 2019 в 08:27
поделиться

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

-1
ответ дан 18 December 2019 в 08:27
поделиться

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

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

Понтеры a и d (значения l) различаются с точки зрения интерфейса. Это определяет, как вы можете относиться к ним с точки зрения кода. Таким образом, даже если 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
ответ дан 18 December 2019 в 08:27
поделиться

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

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 по-прежнему является Animal, вы не можете предоставить 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

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

2
ответ дан 18 December 2019 в 08:27
поделиться

Это различие важно, когда вы вызываете виртуальную функцию с помощью указателя. Допустим, у 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
ответ дан 18 December 2019 в 08:27
поделиться

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

0
ответ дан 18 December 2019 в 08:27
поделиться
Другие вопросы по тегам:

Похожие вопросы: