Почему производный класс не будет работать в массиве? (C ++) - PullRequest
3 голосов
/ 03 мая 2009

Я создал класс с именем vir, с функцией move:

class vir
{
public:
     vir(int a,int b,char s){x=a;y=b;sym=s;}
     void move(){}
};

(он получен из класса с переменными int x, int y и char sym) Из этого я получил класс, называемый subvir:

class subvir:public vir
{
public:
     subvir(int a,int b,char s){x=a;y=b;sym=s;}
     void move();
};
subvir::move()
{
     x++;
     return;
}

А потом я создал массив vir и поместил в него subvir

subvir sv1(0,0,'Q');
vir vir_RA[1]={sv1};

Но когда я пытаюсь использовать sv1.move ():

vir_RA [0] .move ();

Используется ход vir ({}), а не subvir ({x ++}). Я попытался сделать sv1 вир и vir_RA вир, и это работает, и это также работает, когда я делаю их оба subvir, но мне нужно, чтобы они были разными. Я попытался сделать vir :: move () чисто виртуальным, но затем я получил ошибку, подтверждающую массив. Кто-нибудь знает, как я могу заставить move () работать, когда я использую его из массива?

Ответы [ 6 ]

8 голосов
/ 03 мая 2009

Базовый класс должен иметь virtual функции, чтобы получить то, что вы хотите, если сделать их чистыми, то получится абстрактный базовый класс - то, что вы не можете создать. Однако вы все равно можете создавать указатели / ссылки на абстрактные базовые классы и назначать им объекты производных классов. Ваш базовый класс лучше всего представлен как:

class vir
{
public:
     vir(int a,int b,char s){x=a;y=b;sym=s;}
     virtual void move(){}
};

Это также делает виртуальный move производного класса. Однако в вашем move определении отсутствует возвращаемое значение, и он не будет компилироваться. Попробуйте:

void subvir::move()
{
     x++;
     return;
}

Обратите внимание, что вам нужны либо указатели (как упоминалось в других ответах), либо ссылки на производные классы для динамической привязки к работе. Таким образом, вместо массива vir объектов используйте массив указателей базового класса:

vir* v[ 2 ] = { new subvir(0, 0, 'Q'), new subvir(10, -10, 'P') };

Вы должны также f Прочтите следующие разделы C ++ FAQ Lite:

8 голосов
/ 03 мая 2009

Вы столкнулись с проблемой под названием slicing . Используйте массив указателей или что-то вроде Boost.ptr_container .

5 голосов
/ 03 мая 2009

Две вещи. Массив является массивом vir, поэтому, конечно, он использует vir :: move. move () не является виртуальным методом.

Но важнее нарезка. Вы не можете поместить подклассы в массив. Если sizeof vir! = Sizeof subvir, массив выстроится неправильно. В настоящее время они одинакового размера. Но что будет, если это не так?

5 голосов
/ 03 мая 2009

В этом случае вам нужен массив указателей, а не массив экземпляров. Используйте vir * [] вместо vir []

2 голосов
/ 03 мая 2009

Да, в основном, компилятор не допускает подклассы в массивах, потому что массивы инициализируются плотно для размера шрифта, а подтипы имеют тенденцию быть больше, чем родители, и это может привести к проблемам, если бы вы могли инициализировать массивы значениями подтипа. Что действительно происходит, так это то, что компилятор сначала выделяет байты массива N * size (base_type). И затем он копирует размер (base_type) байтов каждой инициализации объекты. если бы они были разных типов, они бы обрезались, и странные вещи могут произойти в вашем коде.

1 голос
/ 03 мая 2009

Позвольте мне обобщить предыдущие ответы.

Здесь на самом деле есть две проблемы. Одним из них является нарезка. Вы инициализируете массив virs с копией subvir. В таких случаях компилятор вырезает часть vir из subvir и копирует ее в массив, так что вы действительно получаете только объекты vir. Теперь, в вашем конкретном случае, subvir не имеет дополнительных элементов данных, кроме элементов vir, поэтому срезы несколько вырождены, и объект vir очень похож на subvir. Однако vir и subvir - это разные классы, и объект в массиве оказывается объектом vir, а не объектом subvir, замаскированным под vir. Один из способов, с помощью которого различия между этими двумя фактами проявились бы практически, даже если бы оба имели одинаковые элементы данных, заключается в том, что в vir были виртуальные функции, перегруженные subvir. В этом случае указатель vtable в объекте в массиве будет указывать на vtable vir, а не subvir. Конечно, было бы еще более явно, если бы subvir содержал дополнительные элементы данных, которых нет в vir.

Второй вопрос - полиморфизм. В момент использования (вызов move ()) компилятор думает, что вы вызываете метод move () объекта типа vir (так как массив является массивом virs). (Компилятор, конечно, правильно думает, так как из-за среза, вырожденного, каким он может быть в этом случае.) Если бы это был фактически объект subvir, как вы и предполагали, вы могли бы вызвать subvir :: move (), вызвав move ( ) виртуальный в вир.

Чтобы получить желаемое поведение, вы можете использовать массив указателей (но тогда вы будете работать непосредственно с sv1, а не с его копией, если вы сначала не создали копию и не инициализировали массив указателем на копию).

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