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

0

Я изучал виртуальные функции и указатели. Ниже код заставлял меня задуматься, зачем нужна виртуальная функция, когда мы можем набирать указатель базового класса, как мы хотим?

class baseclass {
public:
    void show() {
        cout << "In Base\n";
    }
};

class derivedclass1 : public baseclass {
public:
    void show() {
        cout << "In Derived 1\n";
    }
};

class derivedclass2 : public baseclass {
public:
    void show() {
        cout << "In Derived 2\n";
    }
};

int main(void) {
    baseclass * bptr[2];
    bptr[0] = new derivedclass1;
    bptr[1] = new derivedclass2;

    ((derivedclass1*) bptr)->show();
    ((derivedclass2*) bptr)->show();

    delete bptr[0];
    delete bptr[1];

    return 0;
}

Дает тот же результат, если мы используем виртуальный в базовом классе.

In Derived 1
In Derived 2

Я что-то упускаю?

  • 2
    При использовании полиморфизма вы обычно не знаете производного типа. У вас есть несколько указателей на базовый класс и оперируете ими. Кроме того, вы не можете использовать его для вызова деструктора, поэтому база должна быть полиморфной в любом случае.
  • 0
    Идея состоит в том, что вам не нужно кастовать. Вам не нужно беспокоиться или знать что-либо о фактическом типе, стоящем за указателем базового типа.
Показать ещё 2 комментария
Теги:
inheritance
virtual-functions

3 ответа

2
Лучший ответ

Ваш пример работает, потому что нет данных, нет виртуальных методов и нет множественного наследования. Попробуйте добавить int value; к производному derivedclass1, const char *cstr; к производному derivedclass2, инициализируйте их в соответствующих конструкторах и добавьте их в соответствующие методы show().

Вы увидите, как show() будет печатать значение мусора (если вы derivedclass1 указатель на производный derivedclass1 если это не так) или сбой (если вы derivedclass2 указатель на derivedclass2 класс derivedclass2 если класс фактически не относится к этому типу) или ведут себя иначе странно.


C++ Функции члена класса. Методы AKA - это не что иное, как функции, которые принимают один скрытый дополнительный аргумент, this указатель, и они предполагают, что он указывает на объект правильного типа. Поэтому, когда у вас есть объект типа производный derivedclass1, но вы derivedclass1 на него указатель на тип производного derivedclass2, то то, что происходит без виртуальных методов, таково:

  • метод производного derivedclass2 вызван, потому что хорошо, вы явно сказали "это указатель на производный derivedclass2 ".
  • метод получает указатель на реальный объект, this. Он считает, что это указывает на фактический экземпляр производного derivedclass2, который имел бы определенные элементы данных при определенных смещениях.
  • если объект фактически является производным derivedclass1, эта память содержит нечто совершенно иное. Поэтому, если метод считает, что есть указатель на символ, но на самом деле его нет, тогда доступ к данным, на которые он указывает, вероятно, будет иметь доступ к незаконному адресу и сбою.

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


Таким образом, приведение типов не является альтернативой виртуальным методам. И, в качестве примечания, каждый тип роли - это место, где можно спросить: "Почему это приложено здесь? Есть ли какая-то фундаментальная проблема с этим программным обеспечением, если ему нужна акция здесь?". Законные варианты использования для типоразмеров довольно редки, особенно с объектами ООП. Кроме того, никогда не используйте стили типа C с указателями объектов, используйте static_cast и dynamic_cast если вам действительно нужно сделать бросок.

  • 0
    Да, программа выдает результат мусора. Но почему? Я думал, что приведение типа является альтернативным вариантом для Виртуальной функции.
  • 0
    @ Marquee777 подумайте о виртуальной функции, у нее есть неявный первый параметр this который указывает на структуру данных в памяти, которая содержит указатель на таблицу vTable и данные для этого объекта. Если вы наберете приведение типа другого типа, то представление данных больше не будет действительным для этого типа. Таким образом, мусор.
Показать ещё 1 комментарий
2

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

Тип-литье всегда опасно и может привести к ошибкам во время выполнения больших программ.

Ваш код должен быть открыт для расширения, но закрыт для внесения изменений.

Надеюсь это поможет.

1

Вам нужны виртуальные функции, если вы не знаете производного типа до времени выполнения (например, когда это зависит от ввода пользователя).

В вашем примере у вас есть жестко закодированные отливки для производного derivedclass2 и производного derivedclass1. Теперь, что бы вы сделали здесь?

void f(baseclass * bptr)
{
    // call the right show() function
}

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

Две другие проблемы в вашем примере кода:

  1. Использование C-стиля вместо C++ -строчный dynamic_cast (конечно, вам вообще не нужно бросать в любом случае, когда вы используете виртуальные функции для проблемы, которую они предназначены для решения).
  2. Обработка полимеров полиморфно. См. Пункт 3 в Scott Meyer. Более эффективная книга C++ ("Никогда не обрабатывайте массивы полиморфно").

Ещё вопросы

Сообщество Overcoder
Наверх
Меню