class Circle:public Shape { /* ... */ };
void fv(vector
void f(Shape &);
void g(vector
{
f(d); // OK: неявное преобразование класса Circle в класс Shape
fv(vd); // ошибка: нет преобразования из класса vector
// в класс vector
}
poor()
очень плох, но можно ли рассматривать этот код с точки зрения встроенной системы; иначе говоря, следует ли беспокоиться о таких проблемах в приложениях, для которых важным является безопасность или производительность? Можем ли мы объявить этот код опасным при программировании обычных систем и просто сказать им: “Не делайте так”. Многие современные встроенные системы основаны на графическом пользовательском интерфейсе, который практически всегда организован в соответствии с принципами объектно-ориентированного программирования. К таким примерам относятся пользовательский интерфейс устройств iPod, интерфейсы некоторых мобильных телефонов и дисплеи операторов в системах управления полетами. Кроме того, контроллеры аналогичных устройств (например, множество электромоторов) образуют классические иерархии классов. Другими словами, этот вид кода — и, в частности, данный вид объявлений функции — вызывает особые опасения. Нам нужен более безопасный способ передачи информации о коллекциях данных, который не порождал бы значительных проблем.
void poor(Shape* p, int sz);
исчезает при использовании функции
void general(vector
Если вы программируете систему, в которой допускаются объекты класса std::vector
(или его эквиваленты), то просто последовательно используйте в интерфейсах класс vector
(или его эквиваленты) и никогда не передавайте встроенный массив с помощью указателя и количества элементов.
Если вы не можете ограничиться использованием класса vector
или его эквивалентов, то оказываетесь на территории, где не бывает простых решений, — даже несмотря на то, что использование класса (Array_ref
) вполне очевидно.
25.4.3. Решение: интерфейсный класс
К сожалению, во многих встроенных системах мы не можем использовать класс std::vector
, потому что он использует свободную память. Мы можем решить эту проблему, либо предложив особую реализацию класса vector
, либо (что более просто) используя контейнер, напоминающий класса vector
, но не содержащий его механизма управления памятью. Прежде чем описать такой интерфейсный класс, перечислим его желательные свойства.
• Он знает свой размер (а значит, способен проверять выход за пределы допустимого диапазона).
• Он знает точный тип своих элементов (а значит, не может порождать ошибки, связанные с типами).
• Его несложно передать (скопировать) как пару (указатель, счетчик).
• Его нельзя неявно преобразовать в указатель.
• Он позволяет легко выделить поддиапазон в целом диапазоне.
• Его легко использовать как встроенный массив.
Свойство “легко использовать как встроенный массив” можно обеспечить лишь приблизительно. Если бы мы сделали это совершенно точно, то вынуждены были бы смириться с ошибками, которых стремимся избежать.
Рассмотрим пример такого класса.
template
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
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
).