Вы задаете вопрос и приводите пример кода, который не работает, но по другой причине. Из формулировки вашего вопроса:
Почему для полиморфизма требуются ссылки / указатели?
struct base {
virtual void f();
};
struct derived : public base {
virtual void f();
};
void call1( base b ) {
b.f(); // base::f
}
void call2( base &b ) {
b.f(); // derived::f
}
int main() {
derived d;
call1(d);
call2(d);
}
Когда вы используете семантику передачи по значению (или сохраняете производные элементы в базовом контейнере), вы создаете копии типа base
элементов типа derived
. Это называется слайсинг , поскольку он напоминает тот факт, что у вас есть derived
объект, а вы вырезаете / вырезаете только подобъект base
из него. В этом примере call1
не работает с объектом d
в основном, а скорее с временным типом base
, и вызывается base::f
.
В методе call2
вы передаете ссылку на объект base
. Когда компилятор увидит call2(d)
в основном, он создаст ссылку на подобъект base
в d
и передаст ее функции. Функция выполняет операцию над ссылкой типа base
, которая указывает на объект типа derived
, и будет вызывать derived::f
. То же самое происходит с указателями, когда вы получаете base *
в derived
объект, объект по-прежнему derived
.
Почему я не могу передать контейнер с указателями derived
в функцию, принимающую контейнер с указателями base
?
_Ясно, если derived
равны base
, контейнер derived
равен контейнеру base
.
Нет. Контейнеры derived
не являются контейнерами base
. Это сломало бы систему типов. Самый простой пример использования контейнера derived
в качестве контейнера base
объектов, нарушающих систему типов, приведен ниже.
void f( std::vector<base*> & v )
{
v.push_back( new base );
v.push_back( new another_derived );
}
int main() {
std::vector<derived*> v;
f( v ); // error!!!
}
Если бы строка, помеченная ошибкой, была разрешена языком, то это позволило бы приложению вставлять элементы, не относящиеся к типу derived*
, в контейнер, и это означало бы много проблем ...
Но вопрос был о контейнерах типов значений ...
Когда у вас есть контейнеры типов значений, элементы копируются в контейнер. Вставка элемента типа derived
в контейнер типа base
создаст копию подобъекта типа base
внутри объекта derived
. Это то же самое нарезка , что и выше. Помимо того, что это ограничение языка, есть веская причина, когда у вас есть контейнер из base
объектов, у вас есть место для хранения только base
элементов. Вы не можете хранить большие объекты в одном контейнере. В противном случае компилятор даже не будет знать, сколько места осталось зарезервировать для каждого элемента (что будет, если мы позже расширим тип еще больше?).
В других языках может показаться, что это действительно разрешено (Java), но это не так. Единственное изменение в синтаксисе. Когда у вас есть String array[]
в Java, вы фактически пишете эквивалент string *array[]
в C ++. Все не примитивные типы являются ссылками на языке, и тот факт, что вы не добавляете *
в синтаксис, не означает, что контейнер содержит экземпляров String , контейнеры содержат ссылок в строки, которые больше относятся к указателям на c ++, чем к ссылкам на c ++.