Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
тип_возвращаемого_значения StrBlob::имя_члена(список_парам)
соответствующий член шаблона Blob будет выглядеть так:
template <typename Т>
тип_возвращаемого_значения Blob<Т>::имя_члена(список_парам)
Функция check() и функции доступа к членамНачнем с определения функции-члена check(), проверяющей предоставленный индекс:
template <typename Т>
void Blob<T>::check(size_type i, const std::string &msg) const {
if (i >= data->size())
throw std::out_of_range(msg);
}
Кроме отличия в имени класса и использовании списка параметров шаблона, эта функция идентична первоначальной функции-члену класса StrBlob.
Оператор индексирования и функция back() используют параметр шаблона для определения типа возвращаемого значения, но в остальном они неизменны:
template <typename Т>
Т& Blob<T>::back() {
check(0, "back on empty Blob");
return data->back();
}
template <typename T>
T& Blob<T>::operator[](size_type i) {
// если i слишком велико, check() передаст сообщение и предотвратит
// доступ к несуществующему элементу
check(i, "subscript out of range");
return (*data)[i];
}
В первоначальном классе StrBlob эти операторы возвращали тип string&. Шаблонная версия возвращает ссылку на любой тип, использованный при создании экземпляра шаблона Blob.
Функция pop_back() почти идентична оригинальной функции-члену класса StrBlob:
template <typename Т> void Blob<T>::pop_back() {
check(0, "pop_back on empty Blob");
data->pop_back();
}
Оператор индексирования и функция-член back() перегружены как const. Оставим определение этих функций-членов и функции front() читателю в качестве самостоятельного упражнения.
Конструкторы Blob()Подобно любым другим функциям-членам, определенным вне шаблона класса, конструктор начинается с объявления параметров шаблона для шаблона класса, членом которого он является:
template <typename Т>
Blob<T>::Blob(): data(std::make_shared<std::vector<T>>()) { }
Здесь функция-член Blob() определяется в пределах шаблона Blob<T>. Как и стандартный конструктор StrBlob() (см. раздел 12.1.1), данный конструктор резервирует пустой вектор и сохраняет указатель на него в переменной data. Как уже упоминалось, в качестве аргумента резервируемого шаблона vector используется собственный параметр типа класса.
Точно так же конструктор, получающий параметр типа initializer_list, использует свой параметр типа T как тип элемента для своего параметра типа initializer_list:
template <typename Т>
Blob<T>::Blob(std::initializer_list<T> il):
data(std::make_shared<std::vector<T>>(il)) { }
Подобно стандартному конструктору, этот конструктор резервирует новый вектор. В данном случае этот вектор инициализируется из параметра il.
Чтобы использовать этот конструктор, следует передать список инициализации, тип элементов которого совместим с типом элемента Blob:
Blob<string> articles = {"a", "an", "the"};
Параметр этого конструктора имеет тип initializer_list<string>. Каждый строковый литерал в списке неявно преобразуется в тип string.
Создание функций-членов шаблона классаПо умолчанию экземпляр функции-члена шаблона класса создается, только если программа использует эту функцию-член. Рассмотрим следующий код:
// создает экземпляр Blob<int> и конструктор initializer_list<int>
Blob<int> squares = {0,1,2,3,4,5,6,7,8,9};
// создает экземпляр Blob<int>::size() const
for (size_t i = 0; i != squares.size(); ++i)
squares[i] = i*i; // создает экземпляр Blob<int>::operator[](size_t)
Этот код создает экземпляр класса Blob<int> и трех его функций-членов: operator[](), size() и конструктора initializer_list<int>().
Если функция-член не используется, ее экземпляр не создается. Благодаря этому факту можно создавать экземпляры класса, используя типы, которые не отвечают требованиям для некоторых из операций шаблона (см. раздел 9.2).
По умолчанию экземпляр члена шаблона класса создается, только если он используется.
Упрощение использования имени шаблона класса в коде классаИз правила, согласно которому следует предоставить аргументы шаблона при использовании шаблона класса, есть одно исключение. В области видимости самого шаблона класса имя шаблона можно использовать без аргументов:
// BlobPtr передает исключение при попытке доступа к несуществующему
// элементу
template <typename Т> class BlobPtr
public:
BlobPtr(): curr(0) { }
BlobPtr(Blob<T> &a, size_t sz = 0):
wptr(a.data), curr(sz) { } T& operator*() const {
auto p = check{curr, "dereference past end");
return (*p)[curr]; // (*p) - вектор, на который указывает этот
// объект
}
// инкремент и декремент
BlobPtr& operator++(); // префиксные операторы
BlobPtr& operator--();
private:
// если проверка успешна, check() возвращает shared_ptr на вектор
std::shared_ptr<std::vector<T>>
check(std::size_t, const std::string&) const;
// хранит weak_ptr, а значит, базовый вектор может быть удален
std::weak_ptr<std::vector<T>> wptr;
std::size_t curr; // текущая позиция в пределах массива
};
Внимательные читатели, вероятно, обратили внимание на то, что префиксные функции-члены инкремента и декремента шаблона класса BlobPtr возвращают тип BlobPtr&, а не BlobPtr<T>&. В области видимости шаблона класса компилятор рассматривает ссылки на сам шаблон так, как будто были подставлены аргументы шаблона, соответствующие собственным параметрам. Таким образом, этот код эквивалентен следующему:
BlobPtr<T>& operator++();
BlobPtr<T>& operator--();
Использование имени шаблона класса вне тела шаблонаПри определении функций-членов вне тела шаблона класса следует помнить, что код находится не в области видимости класса, пока не встретилось имя класса (см. раздел 7.4):
// постфикс: осуществляет инкремент/декремент объекта, но возвращает
// неизменное значение
template <typename Т>
BlobPtr<T> BlobPtr<T>::operator++(int) {
// никакой проверки здесь не нужно; ее выполнит вызов префиксного
// инкремента
BlobPtr ret = *this; // сохранить текущее значение
++*this; // перемещение на один элемент; префиксный ++
// проверяет инкремент
return ret; // возвратить сохраненное состояние
}
Поскольку тип возвращаемого значения присутствует вне области видимости класса, следует указать, что он возвращает экземпляр BlobPtr, созданный с тем же типом, что и класс. В теле функции код находится в пределах класса, поэтому не нужно повторять аргумент шаблона при определении ret. Когда аргументы шаблона не предоставлены, компилятор подразумевает, что используется тот же тип, что и при создании экземпляра функции-члена. Следовательно, определение ret будет эквивалентно следующему:
BlobPtr<T> ret = *this;
В области видимости шаблона класса можно обращаться к шаблону, не определяя его аргументы.
Шаблоны классов и дружественные отношенияКогда класс объявляет дружественные отношения (см. раздел 7.2.1), класс и его друг могут быть или не быть шаблонами. Шаблон класса, у которого есть друг, не являющийся шаблоном, предоставляет дружественный доступ ко всем экземплярам шаблона. Когда друг сам является шаблоном, предоставляющий дружественные отношения класс контролирует, распространяются ли они на все экземпляры шаблона или только на некий специфический экземпляр.
Дружественные отношения "один к одному"Наиболее распространенная форма дружественных отношений между шаблоном класса и другим шаблоном (класса или функции) подразумевает дружбу между соответствующими экземплярами класса и его друга. Например, класс Blob должен объявить дружественным класс BlobPtr и шаблонную версию оператора равенства класса Blob (первоначально определенную для класса StrBlob в упражнении раздела 14.3.1).