Вход/Регистрация
Программирование. Принципы и практика использования C++ Исправленное издание
вернуться

Страуструп Бьерн

Шрифт:

void f(vector<Shape*>& v)

{

v.push_back(new Rectangle(Point(0,0),Point(100,100)));

}

Очевидно, что мы можем записать указатель
Rectangle*
в объект класса
vector<Shape*>
. Однако, если бы этот объект класса
vector<Shape*>
в каком-то месте программы рассматривался как объект класса
vector<Circle*>
, то мог бы возникнуть неприятный сюрприз. В частности, если бы компилятор пропустил пример, приведенный выше, то что указатель
Rectangle*
делал в векторе
vpc
? Наследование — мощный и тонкий механизм, а шаблоны не расширяют его возможности неявно. Существуют способы использования шаблонов для выражения наследования, но эта тема выходит за рамки рассмотрения этой книги. Просто запомните, что выражение “
D
— это
B
” не означает: “
C<D>
— это
C<B>
” для произвольного шаблонного класса
C
. Мы должны ценить это обстоятельство как защиту против непреднамеренного нарушения типов. (Обратитесь также к разделу 25.4.4.)

19.3.4. Целые типы как шаблонные параметры

Очевидно, что параметризация классов с помощью типов является полезной. А что можно сказать о параметризации классов с помощью, например, целых чисел или строк? По существу, любой вид аргументов может оказаться полезным, но мы будем рассматривать только типы и целочисленные параметры. Другие виды параметров реже оказываются полезными, и поддержка языком С++ других видов параметров носит более сложный характер и требует обширных и глубоких знаний.

Рассмотрим пример наиболее распространенного использования целочисленного значения в качестве шаблонного аргумента: контейнер, количество элементов которого известно уже на этапе компиляции.

template<class T, int N> struct array {

T elem[N]; // хранит элементы в массиве -

// члене класса, использует конструкторы по умолчанию,

// деструктор и присваивание

T& operator[] (int n); // доступ: возвращает ссылку

const T& operator[] (int n) const;

T* data { return elem; } // преобразование в тип T*

const T* data const { return elem; }

int size const { return N; }

}

Мы можем использовать класс

array
(см. также раздел 20.7) примерно так:

array<int,256> gb; // 256 целых чисел

array<double,6> ad = { 0.0, 1.1, 2.2, 3.3, 4.4, 5.5 }; // инициализатор!

const int max = 1024;

void some_fct(int n)

{

array<char,max> loc;

array<char,n> oops; // ошибка: значение n компилятору

// неизвестно

// ...

array<char,max> loc2 = loc; // создаем резервную копию

// ...

loc = loc2; // восстанавливаем

// ...

}

Ясно, что класс array очень простой — более простой и менее мощный, чем класс

vector
, — так почему иногда следует использовать его, а не класс
vector
? Один из ответов: “эффективность”. Размер объекта класса array известен на этапе компиляции, поэтому компилятор может выделить статическую память (для глобальных объектов, таких как
gb
) или память в стеке (для локальных объектов, таких как
loc
), а не свободную память. Проверяя выход за пределы диапазона, мы сравниваем константы (например, размер N). Для большинства программ это повышение эффективности незначительно, но если мы создаем важный компонент системы, например драйвер сети, то даже небольшая разница оказывается существенной. Что еще более важно, некоторые программы просто не могут использовать свободную память. Такие программы обычно работают во встроенных системах и/или в программах, для которых основным критерием является безопасность (подробно об этом речь пойдет в главе 25). В таких программах массив
array
имеет много преимуществ над классом vector без нарушения основного ограничения (запрета на использование свободной памяти).

Поставим противоположный вопрос: “Почему бы просто не использовать класс

vector
?”, а не “Почему бы просто не использовать встроенные массивы?” Как было показано в разделе 18.5, массивы могут порождать ошибки: они не знают своего размера, они конвертируют указатели при малейшей возможности и неправильно копируются; в классе
array
, как и в классе
vector
, таких проблем нет. Рассмотрим пример.

double* p = ad; // ошибка: нет неявного преобразования

// в указатель

double* q = ad.data; // OK: явное преобразование

template<class C> void printout(const C& c) // шаблонная функция

{

for (int i = 0; i<c.size; ++i) cout << c[i] <<'\n';

Эту функцию

printout
можно вызвать как в классе
array
, так и в классе
vector
.

printout(ad); // вызов из класса array

  • Читать дальше
  • 1
  • ...
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • ...

Ебукер (ebooker) – онлайн-библиотека на русском языке. Книги доступны онлайн, без утомительной регистрации. Огромный выбор и удобный дизайн, позволяющий читать без проблем. Добавляйте сайт в закладки! Все произведения загружаются пользователями: если считаете, что ваши авторские права нарушены – используйте форму обратной связи.

Полезные ссылки

  • Моя полка

Контакты

  • chitat.ebooker@gmail.com

Подпишитесь на рассылку: