Виртуальное ключевое слово делает недоступным метод из производных объектов - PullRequest
1 голос
/ 12 октября 2011

У меня есть этот код:

#include <iostream>

class Super{
public:
    virtual void showName();
};

class Special1 : public Super {
public:
    void showName();
    void sayHello();
};

class Special2 : public Super {
public:
    void showName();
    void sayGoodbye();
};

void Super::showName() {
    std::cout << "I'm super!" << std::endl;
}

void Special1::showName() {
    std::cout << "I'm special1" << std::endl;
}

void Special1::sayHello() {
    std::cout << "Hello" << std::endl;
}

void Special2::showName() {
    std::cout << "I'm special2" << std::endl;
}

void Special2::sayGoodbye() {
    std::cout << "Goodbye" << std::endl;
}

int main () {
    Super *oSpec=new Super;

    Special1 *o1=static_cast<Special1 *>(oSpec);
    Special2 *o2=static_cast<Special2 *>(oSpec);

    oSpec->showName();
    o1->showName();
    o2->showName();

    o1->sayHello();
    o2->sayGoodbye();

    delete oSpec;

    return 0;
}

Когда я запускаю его, он показывает следующий вывод:

I'm super!
I'm super!
I'm super!
Hello
Goodbye

Но если я уберу ключевое слово virtual из объявления класса Super:

class Super{
public:
    /*virtual*/ void showName();
};

Вывод становится правильным:

I'm super!
I'm special1
I'm special2
Hello
Goodbye

Тогда у меня такой вопрос: почему наличие ключевого слова virtual заставляет указатели o1 и o2 запускать метод Super::showName() вместо Special1::showName() или Special2::showName()?

Ответы [ 4 ]

2 голосов
/ 12 октября 2011

Потому что ваш код в main неверен.Все объекты говорят: «Я супер», потому что для них (на самом деле на заднем плане) наиболее производный тип по-прежнему super.Вот как они были созданы.

Ваше статическое приведение нарушает все правила, чтобы быть неопределенным поведением (все может случиться), потому что вы говорите компилятору, что o1 - это Special1, хотя на самом делеэто не тип Special1.Функции

virtual обычно полезны, когда вы создаете группу производных объектов и сохраняете их в указателе / ​​контейнере, который содержит указатели на объект base .А не наоборот.Вы хотите создать указатели типа Special1 и Special2 и сохранить их в указателях на super.

2 голосов
/ 12 октября 2011

Ваше приведение (Special1 *o1=static_cast<Special1 *>(oSpec);) является неопределенным поведением в обоих случаях, поэтому любой вывод вообще приемлем для языка. Вы взяли объект типа Super и обманули компилятор, сказав ему, что это действительно производный класс. В этом случае происходит то, что вы все равно получаете результат родительского класса Super, который действительно есть.

2 голосов
/ 12 октября 2011

Вывод ближе к правильному с virtual и неверному с ним (если вы действительно этого не хотели).Приведение не меняет тип объектов, а только тип указателей.Они по-прежнему имеют тип Super, и поэтому должен выполняться Super::showName.

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

Классический пример того, почему вы используете виртуальные функции, предназначен для музыкантов.У вас может быть функция, которая заставляет весь оркестр играть, вызывая метод Play на каждом пройденном им Musician *.Для Pianist, который должен вызывать Pianist::Play.

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

Кстати, вы все равно можете вызывать метод базового класса, используя область действияпереопределения.Например, o1->Super::showName();.

Фактически результат, который вы называете «правильным», катастрофичен.Запуск Special1::showName() с указателем this, указывающим на объект, который не относится к типу Special1 (или к чему-то производному от него), является неопределенным поведением и может легко привести к падению.

1 голос
/ 12 октября 2011

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

Здесь:

Super *oSpec=new Super;

oSpec указывает на объект type Super.

Таким образом, в этих утверждениях.Приведения имеют неопределенное поведение, так как объект, на который указывает oSpec, не является Специальным 1 или Специальным 2).

Special1 *o1=static_cast<Special1 *>(oSpec);
Special2 *o2=static_cast<Special2 *>(oSpec);

Если вы используете dynamicica_cast <> (как вы должны делать при приведении иерархии классов),Вы найдете результат NULL.

Special1 *o1=dynamic_cast<Special1 *>(oSpec);
Special2 *o2=dynamic_cast<Special2 *>(oSpec);

std::cout << "O1(" << (void*)o1 << ") O2(" << (void*)o2 << ")\n";

Это покажет, что оба указателя равны NULL.

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