2015-11-06 2 views
1

Чтобы лучше понять мой вопрос, я имею в виду тему, обсуждаемую в книге «Язык программирования C++ 4-го издания», глава 27, раздел 2.1.Почему примитивный массив полиморфных типов опасен

Автор говорит об опасности полиморфных типов и встроенных массивов. Он приводит следующий пример:

void maul(Shape∗ p, int n) // Danger! 
{ 
    for (int i=0; i!=n; ++i) 
     p[i].draw(); //looks innocent; it is not 
} 

void user() 
{ 
    Circle image[10]; // an image is composed of 10 Circles 
    // ... 
    maul(image,10); // ‘‘maul’’ 10 Circles 
    // ... 
} 

Мы сказали, что форма является абстрактным размер размера 4 и Circle наследует Shape и добавить дополнительный 2 членов, центр и радиус, который добавляет к размеру типа, следовательно, sizeof(Circle)>sizeof(Shape). Теперь автор объясняет, что, учитывая следующее мнение, например:

user() view: image[0] image[1] image[2] image[3]<br/> 
maul() view: p[0] p[1] p[2] p[3] 

Вызов p[1].draw() (акцент на р [], при р [0] будет называть правильной функции) потерпит неудачу, потому что там нет указателя виртуальной функции, где это ожидалось.

Теперь я знаю, как работают таблицы виртуальных функций, но я не понимаю, как размер типа или его макета влияет на вызов виртуальной функции? Когда компилятор видеть вызов виртуальной функции она не заменить его чем-то похожее на:

p[1]._vfptr->draw_impl(); 

Предполагая, что я прав, то как размер полученного объекта/его расположение нарушило призыв Это.

+3

Как вы ожидаете совершить вызов виртуальной функции, если у вас даже нет правильных адресов объектов «Круг»? – Brian

+0

Вы понимаете, что 'image + 1! = P + 1'? – Jarod42

+0

Спасибо, что объяснил, я читал про нарезку, и это дало понять мне. –

ответ

0

Когда i является 0, это работает:

p[i].draw(); // Shape* p 

p[0] таким же, как *(p+0) который только *p, и вызов функции через указатель базового класса является то, что виртуальные функции предназначены для поддержки.

Но как насчет того, когда i является 1? Теперь у вас есть p[1], то есть *(p+1). А что такое p+1, ну, это адрес «следующего» объекта после p. Но индексирование массива представляет собой концепцию из C, где нет виртуальных таблиц или производных классов, поэтому индексирование выполняется путем добавления времени компиляцииsizeof(*p), которое равно sizeof(Shape). Вы в конечном итоге что-то вроде:

((Shape*)((char*)p + sizeof(Shape)))->draw(); 

То есть приращение p (начало массива Circle с) по sizeof(Shape) байт, а затем разыменования его называют draw(). Но это индексирование неверно, потому что оно должно быть sizeof(Circle) байт. И C, который дает нам тип массива, не дает нам никаких инструментов, чтобы иметь дело с этим, потому что он не предвидел C++.

Обратите внимание, что 11 std::array<> C++ не поможет, кроме сделать выше ошибки компиляции (который я полагаю намного лучше, чем во время выполнения непредсказуемое поведение вы получаете с массивом C-стиле). И старый добрый std::vector<> такой же. Вы можете сохранить массив указателей, и в этом случае они обычно должны быть умными указателями, такими как std::shared_ptr. Это работает, потому что указатели на любой тип (кроме указателей на функции-члены!) Имеют одинаковый размер, поэтому индексирование по массиву из них отлично работает.

Смежные вопросы