Templates and generic programming
T.81
Не смешивайте иерархии и массивы
Причина
Массив объектов производного класса может неявно «деградировать» до указателя на базовый класс, что приводит к катастрофическим последствиям.
Пример
Предположим, что Apple и Pear — два вида Fruit.
void maul(Fruit* p)
{
*p = Pear{}; // put a Pear into *p
p[1] = Pear{}; // put a Pear into p[1]
}
Apple aa [] = { an_apple, another_apple }; // aa contains Apples (obviously!)
maul(aa);
Apple& a0 = &aa[0]; // a Pear?
Apple& a1 = &aa[1]; // a Pear?
Вероятно, aa[0] окажется Pear (без явного приведения!). Если sizeof(Apple) != sizeof(Pear), обращение к aa[1] не будет выровнено на начало объекта в массиве. Мы получим нарушение типов и, весьма вероятно, повреждение памяти. Никогда не пишите такой код.
Заметьте, что maul() нарушает правило «T* указывает на отдельный объект».
Альтернатива: используйте правильный (шаблонный) контейнер:
void maul2(Fruit* p)
{
*p = Pear{}; // put a Pear into *p
}
vector<Apple> va = { an_apple, another_apple }; // va contains Apples (obviously!)
maul2(va); // error: cannot convert a vector<Apple> to a Fruit*
maul2(&va[0]); // you asked for it
Apple& a0 = &va[0]; // a Pear?
Заметьте, что присваивание в maul2() нарушает правило запрета срезки.
Контроль
- Обнаруживайте этот ужас!