Что делать, если мы хотим обрабатывать коллекцию объектов класса Circle
как коллекцию класса Shape
, т.е. если действительно хотим, чтобы функция better()
(представляющая собой вариант нашей старой знакомой функции draw_all()
; см. разделы 19.3.2 и 22.1.3) реализовала полиморфизм? По существу, мы не можем этого сделать. В разделах 19.3.3 и 25.4.2 показано, что система типов имеет веские основания отказаться воспринимать тип vector
как vector
. По той же причине она отказывается принимать тип Array_ref
как Array_ref
. Если вы не помните, почему, то перечитайте раздел 19.3.3, поскольку данный момент очень важен, даже если это кажется неудобным.
a[i].draw()
в функции better()
противоречит этому требованию. Когда мы видим в этом выражении точку, а не стрелку (–>
), следует ожидать проблем с полиморфизмом
Что нам делать? Во-первых, мы должны работать с указателями (или ссылками), а не с самими объектами, поэтому следует попытаться использовать классы Array_ref
, Array_ref
и тому подобные, а не Array_ref
, Array_ref
и т.п.
Однако мы по-прежнему не можем конвертировать класс Array_ref
в класс Array_ref
, поскольку нам потом может потребоваться поместить в контейнер Array_ref
элементы, которые не имеют типа Circle*
. Правда, существует одна лазейка.
• Мы не хотим модифицировать наш объект класса Array_ref
; мы просто хотим рисовать объекты класса Shape
! Это интересный и совершенно особый случай: наш аргумент против преобразования типа Array_ref
в Array_ref
не относится к ситуациям, в которых мы не хотим модифицировать класс Array_ref
.
• Все массивы указателей имеют одну и ту же схему (независимо от объектов, на которые они ссылаются), поэтому нас не должна волновать проблема, упомянутая в разделе 25.4.2.
Array_ref
будет интерпретироваться как неизменяемый объект класса Array_ref
. Итак, нам достаточно просто найти способ это сделать. Рассмотрим пример
Нет никаких логических препятствий интерпретировать данный массив указателей типа Circle*
как неизменяемый массив указателей типа Shape*
(из контейнера Array_ref
).
better()
так, чтобы она использовала указатели и гарантировала, что мы ничего не напутаем с аргументами контейнера.
void better2(const Array_ref
{
for (int i = 0; i
if (a[i])
a[i]–>draw();
}
Теперь мы работаем с указателями, поэтому должны предусмотреть проверку нулевого показателя. Для того чтобы гарантировать, что функция better2()
не модифицирует наш массив и векторы находятся под защитой контейнера Array_ref
, мы добавили несколько квалификаторов const
. Первый квалификатор const
гарантирует, что мы не применим к объекту класса Array_ref
модифицирующие операции, такие как assign()
и reset()
. Второй квалификатор const
размещен после звездочки (*
). Это значит, что мы хотим иметь константный указатель (а не указатель на константы); иначе говоря, мы не хотим модифицировать указатели на элементы, даже если у нас есть операции, позволяющие это сделать.
Далее, мы должны устранить главную проблему: как выразить идею, что объект класса Array_ref
можно конвертировать
• в нечто подобное объекту класса Array_ref
(который можно использовать в функции better2()
);
• но только если объект класса Array_ref
является неизменяемым.
Это можно сделать, добавив в класс Array_ref
оператор преобразования.
template
class Array_ref {
public:
// как прежде
template
operator const Array_ref
{
// проверка неявного преобразования элементов:
static_cast
(*static_cast
// приведение класса Array_ref:
return Array_ref
(p),sz);
}
// как прежде
};
Это похоже на головоломку, но все же перечислим ее основные моменты.