Я изучал виртуальные функции и указатели. Ниже код заставлял меня задуматься, зачем нужна виртуальная функция, когда мы можем набирать указатель базового класса, как мы хотим?
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
Я что-то упускаю?
Ваш пример работает, потому что нет данных, нет виртуальных методов и нет множественного наследования. Попробуйте добавить 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
если вам действительно нужно сделать бросок.
this
который указывает на структуру данных в памяти, которая содержит указатель на таблицу vTable и данные для этого объекта. Если вы наберете приведение типа другого типа, то представление данных больше не будет действительным для этого типа. Таким образом, мусор.
Если вы используете виртуальные функции, ваш код, вызывающий функцию, не обязательно должен знать о фактическом классе объекта. Вы просто вызываете функцию вслепую, и правильная функция будет выполнена. Это основа полиморфизма.
Тип-литье всегда опасно и может привести к ошибкам во время выполнения больших программ.
Ваш код должен быть открыт для расширения, но закрыт для внесения изменений.
Надеюсь это поможет.
Вам нужны виртуальные функции, если вы не знаете производного типа до времени выполнения (например, когда это зависит от ввода пользователя).
В вашем примере у вас есть жестко закодированные отливки для производного derivedclass2
и производного derivedclass1
. Теперь, что бы вы сделали здесь?
void f(baseclass * bptr)
{
// call the right show() function
}
Возможно, ваше замешательство проистекает из того факта, что вы еще не столкнулись с ситуацией, когда виртуальные функции были действительно полезными. Когда вы всегда точно знаете во время компиляции конкретный тип, над которым работаете, тогда вам вообще не нужны виртуальные функции.
Две другие проблемы в вашем примере кода:
dynamic_cast
(конечно, вам вообще не нужно бросать в любом случае, когда вы используете виртуальные функции для проблемы, которую они предназначены для решения).