Почему мы не можем реализовать полиморфизм в C ++ без указателя или ссылки на базовый класс? - PullRequest
0 голосов
/ 03 февраля 2010



Прежде всего взгляните на следующий код (в этом коде форма является базовым классом, а строка - производным)

void drawshapes(shape sarray[],int size)
{
    for(int i=0;i< size; i++)
        sarray[i].draw();
}

main()
{
   line larray[10];
   larray[0]=line(p1,p2);//assuming that we have a point class
   larray[1]=line(p2,p3);
   ..........
   drawshapes(larray,10);
}


когда мы компилируем эту программу, сначала вызывается метод draw формы, а затем программа завершается.почему это заканчивается?почему мы не можем реализовать полиморфизм без указателя или ссылки на базовый класс, что является технической причиной для этого?какой компилятор будет делать, если мы пытаемся реализовать полиморфизм с массивом объектов?пожалуйста, объясните в понятной форме с примерами.Я буду очень благодарен.

Ответы [ 4 ]

5 голосов
/ 03 февраля 2010

Вы задаете вопрос и приводите пример кода, который не работает, но по другой причине. Из формулировки вашего вопроса:

Почему для полиморфизма требуются ссылки / указатели?

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 ++.

5 голосов
/ 03 февраля 2010

Во-первых: вы смешиваете две концепции: полиморфизм и семантика значения и эталона.

Полиморфизм времени выполнения

Полиморфизм может быть разных форм.В зависимости от используемой среды выполнения доступны другие параметры.

Интерпретируемый язык (например, Ruby, Python, Javascript, ...) допускает «типизацию утки»: если объект просто имеет метод с именем foo, вы можете вызвать его,Обычно эти языки выполняют сборку мусора, поэтому понятие указателей и объектов не слишком уместно.

C ++ имеет другую точку зрения: полиморфизм разрешен, но более строго.Применение общего базового класса (который может быть абстрактным) позволяет компилятору проверять семантику вашего кода: таким образом, компилятор гарантирует, что вы действительно имели в виду метод foo, который реализует намеченный интерфейс, а не некоторую путаницу foos.

Этот полиморфизм реализуется с помощью функции virtual: указателя на функцию, которая может варьироваться в зависимости от реализации.Вызывающая сторона foo сначала должна найти значение указателя функции, а затем перейти к этой функции.

Пока что речь идет о полиморфизме.

Containment

Теперь для сдерживания: если вы создаете массив из line объектов в C ++, эти объекты находятся рядом друг с другом в памяти;они содержат по значению .Когда вы передаете массив функции, вызываемая функция может получить только массив того же типа.В противном случае, сделав шаг sizeof(shape) в массив, мы окажемся в середине line.

. Чтобы исправить это, вы можете содержать объекты «по ссылке» - в C ++ мыдля этого используйте указатели.

полиморфизм времени компиляции

Но есть и другой способ достижения полиморфных функций: шаблоны.Вы можете написать свою drawshapes функцию с аргументом шаблона, который говорит, какой тип объекта вы используете:

template< typename tShape, size_t N > 
void drawshapes( tShape (&aShapes)[N] ) {
    for( tShape* shape=aShapes; shape != aShapes+N; ++shape ) {
        shape->draw();
    }
}

(Примечание: есть функции stl, которые упрощают это, но этовыходит за рамки вопроса.

std::for_each( shapes, shapes+10, mem_fun_ref( &shape::draw ) );

)

3 голосов
/ 03 февраля 2010

Вы не можете передать массив строки вместо массива формы. Вы должны использовать массив указателей. Это происходит потому, что когда функция пытается получить доступ ко второму члену, она делает * (sarray + sizeof (shape)) вместо * (sarray + sizeof (line)), что будет правильным способом доступа ко второму элементу массива линии.

1 голос
/ 03 февраля 2010

Вы хотите что-то вроде этого:

void drawshapes(shape *sarray[],int size)
{
   for(int i=0;i< size; i++)
      sarray[i]->draw();
}

main()
{
   shape *larray[10];
   larray[0] = new line(p1,p2);//assuming that we have a point class
   larray[1] = new line(p2,p3);
   ..........
   drawshapes(larray, 10);
   // clean-up memory
   ...
}
...