}
Хорошо, интерфейс функции
poor()
очень плох, но можно ли рассматривать этот код с точки зрения встроенной системы; иначе говоря, следует ли беспокоиться о таких проблемах в приложениях, для которых важным является безопасность или производительность? Можем ли мы объявить этот код опасным при программировании обычных систем и просто сказать им: “Не делайте так”. Многие современные встроенные системы основаны на графическом пользовательском интерфейсе, который практически всегда организован в соответствии с принципами объектно-ориентированного программирования. К таким примерам относятся пользовательский интерфейс устройств iPod, интерфейсы некоторых мобильных телефонов и дисплеи операторов в системах управления полетами. Кроме того, контроллеры аналогичных устройств (например, множество электромоторов) образуют классические иерархии классов. Другими словами, этот вид кода — и, в частности, данный вид объявлений функции — вызывает особые опасения. Нам нужен более безопасный способ передачи информации о коллекциях данных, который не порождал бы значительных проблем.

Итак, мы не хотим передавать функциям встроенные массивы с помощью указателей и размера массива. Чем это заменить? Проще всего передать ссылку на контейнер, например, на объект класса vector. Проблема, которая возникла в связи с интерфейсом функции
void poor(Shape* p, int sz);
исчезает при использовании функции
void general(vector<Shape>&);
Если вы программируете систему, в которой допускаются объекты класса
std::vector
(или его эквиваленты), то просто последовательно используйте в интерфейсах класс
vector
(или его эквиваленты) и никогда не передавайте встроенный массив с помощью указателя и количества элементов.
Если вы не можете ограничиться использованием класса
vector
или его эквивалентов, то оказываетесь на территории, где не бывает простых решений, — даже несмотря на то, что использование класса (
Array_ref
) вполне очевидно.
25.4.3. Решение: интерфейсный класс
К сожалению, во многих встроенных системах мы не можем использовать класс
std::vector
, потому что он использует свободную память. Мы можем решить эту проблему, либо предложив особую реализацию класса
vector
, либо (что более просто) используя контейнер, напоминающий класса
vector
, но не содержащий его механизма управления памятью. Прежде чем описать такой интерфейсный класс, перечислим его желательные свойства.
• Он ссылается на объекты в памяти (он не владеет объектами, не размещает их, не удаляет и т.д.).
• Он знает свой размер (а значит, способен проверять выход за пределы допустимого диапазона).
• Он знает точный тип своих элементов (а значит, не может порождать ошибки, связанные с типами).
• Его несложно передать (скопировать) как пару (указатель, счетчик).
• Его нельзя неявно преобразовать в указатель.
• Он позволяет легко выделить поддиапазон в целом диапазоне.
• Его легко использовать как встроенный массив.
Свойство “легко использовать как встроенный массив” можно обеспечить лишь приблизительно. Если бы мы сделали это совершенно точно, то вынуждены были бы смириться с ошибками, которых стремимся избежать.
Рассмотрим пример такого класса.
template<class T>
class Array_ref {
public:
Array_ref(T* pp, int s) :p(pp), sz(s) { }
T& operator[ ](int n) { return p[n]; }
const T& operator[ ](int n) const { return p[n]; }
bool assign(Array_ref a)
{
if (a.sz!=sz) return false;
for (int i=0; i<sz; ++i) { p[i]=a.p[i]; }
return true;
}
void reset(Array_ref a) { reset(a.p,a.sz); }
void reset(T* pp, int s) { p=pp; sz=s; }
int size() const { return sz; }
// операции копирования по умолчанию:
// класс Array_ref не владеет никакими ресурсами
// класс Array_ref имеет семантику ссылки
private:
T* p;
int sz;
};
Класс
Array_ref
близок к минимальному.
• В нем нет функций
push_back()
(для нее нужна динамическая память) и
at()
(для нее нужны исключения).
• Класс Array_ref имеет форму ссылки, поэтому операция копирования просто копирует пары (
p, sz
).
• Инициализируя разные массивы, можем получить объекты класса
Array_ref
, которые имеют один и тот же тип, но разные размеры.
• Обновляя пару (
p, size
) с помощью функции
reset()
, можем изменить размер существующего класса
Array_ref
(многие алгоритмы требуют указания поддиапазонов).
• В классе
Array_ref
нет интерфейса итераторов (но при необходимости этот недостаток легко устранить). Фактически концепция класса
Array_ref
очень напоминает диапазон, заданный двумя итераторами.
Класс
Array_ref
не владеет своими элементами и не управляет памятью, он просто представляет собой механизм для доступа к последовательности элементов и их передачи функциям. Иначе говоря, он отличается от класса
array
из стандартной библиотеки (см. раздел 20.9).
Для того чтобы облегчить создание объектов класса
Array_ref
, напишем несколько вспомогательных функций.
template<class T> Array_ref<T> make_ref(T* pp, int s)
{
return (pp) ? Array_ref<T>(pp,s):Array_ref<T>(0,0);