Главная страница
Навигация по странице:

  • Упражнение 15.38. Допустимы ли следующие объявления Если нет, то почему Если да, то что они означают

  • Язык программирования C Пятое издание


    Скачать 1.85 Mb.
    НазваниеЯзык программирования C Пятое издание
    Дата15.07.2019
    Размер1.85 Mb.
    Формат файлаpdf
    Имя файла620354-www.libfox.ru.pdf
    ТипДокументы
    #84130
    страница43 из 54
    1   ...   39   40   41   42   43   44   45   46   ...   54

    Упражнение 15.32. Что будет при копировании, перемещении, присвоении и удалении объекта класса Query?
    Упражнение 15.33. А объектов класса Query_base?
    15.9.3. Производные классы
    Самая интересная часть классов, производных от класса Query_base, в том, как они представляются. Класс WordQuery проще всех. Его задача — хранение искомого слова.
    Page 803/1103

    Другие классы работают на одном или двух операндах. У класса NotQuery один операнд, а у классов AndQuery и OrQuery — по два. Операндами в каждом из этих классов могут быть объекты любого из реальных классов, производных от класса Query_base: NotQuery может быть применен к WordQuery, как и AndQuery, OrQuery или NotQuery. Для обеспечения такой гибкости операнды следует хранить как указатели на класс Query_base. Таким образом,
    можно привязать указатель на любой необходимый реальный класс.
    Но вместо того, чтобы хранить указатель на класс Query_base, классы будут сами использовать объект Query. Подобно тому, как пользовательский код упрощается при использовании класса интерфейса, можно упростить код собственного класса, используя тот же класс.
    Теперь, когда конструкция этих классов известна, их можно реализовать. Класс WordQuery
    Класс WordQuery отвечает за поиск заданной строки. Это единственная операция, которая фактически выполняет запрос для данного объекта класса TextQuery: class WordQuery: public Query_base { friend class Query; //
    Query использует конструктор WordQuery
    WordQuery(const std::string &s) : query_word (s) { }
    // конкретный класс: WordQuery определяет все унаследованные чистые
    // виртуальные функции
    QueryResult eval(const TextQuery &t) const
    { return t.query(query_word); } std::string rep() const { return query_word; } std::string query_word; // искомое слово
    };
    Подобно классу Query_base, у класса WordQuery нет открытых членов; он должен сделать класс Query дружественным, чтобы позволить ему получать доступ к конструктору
    WordQuery().
    Каждый из конкретных классов запроса должен определить унаследованные чистые виртуальные функции eval() и rep(). Обе функции определены в теле класса WordQuery:
    функция eval() вызывает функцию-член query() своего параметра типа TextQuery, который фактически осуществляет поиск в файле; функция rep() возвращает строку, которую данный объект класса WordQuery представляет (т.е. query_word).
    Определив класс WordQuery, можно определить конструктор Query(), получающий строку: inline
    Page 804/1103

    Query::Query(const std::string &s): q(new WordQuery(s)) { }
    Этот конструктор резервирует объект класса WordQuery и инициализирует его указатель-член так, чтобы он указывал на этот недавно созданный объект. Класс NotQuery и оператор


    Оператор подразумевает создание объекта класса NotQuery, содержащего инверсный запрос: class NotQuery: public Query_base { friend Query operator(const Query &);
    NotQuery(const Query &q): query(q) { }
    // конкретный класс: NotQuery определяет все унаследованные
    // чистые виртуальные функции std::string rep() const {return + query.rep() + ")";}
    QueryResult eval(const TextQuery&) const;
    Query query;
    }; inline Query operator(const Query &operand) { return std::shared_ptr<Query_base>(new NotQuery(operand));
    }
    Поскольку все члены класса NotQuery являются закрытыми, объявляем оператор
    дружественным. Чтобы отобразить объект класса NotQuery, следует вывести символ ""
    сопровождаемый основным запросом. Чтобы сделать приоритет очевидным для читателя,
    заключим запрос в скобки.
    Следует заметить то, что вызов функции rep() объекта класса NotQuery в конечном счете приводит к виртуальному вызову функции собственной функции-члена rep(): query.rep() — это невиртуальный вызов функции-члена rep() класса Query. Функция Query::rep() в свою очередь осуществляет вызов q->rep(), являющийся виртуальным вызовом через указатель
    Query_base.
    Оператор динамически резервирует новый объект класса NotQuery. Оператор return
    (неявно) использует конструктор Query(), получающий указатель shared_ptr<Query_base>.
    Таким образом, оператор return эквивалентен следующему:
    // резервировать новый объект NotQuery
    // связать новый объект NotQuery с указателем shared_ptr<Query_base>
    Page 805/1103
    shared_ptr<Query_base> tmp(new NotQuery(expr)); return Query(tmp); // использовать конструктор Query(), получающий
    // указатель shared_ptr
    Функция-член eval() достаточно сложна, поэтому реализуем ее вне тела класса. Более подробно функция eval() рассматривается в разделе 15.9.4. Класс BinaryQuery
    Класс BinaryQuery — это абстрактный базовый класс, содержащий данные, необходимые двум классам запроса, AndQuery и OrQuery, которые используют по два операнда: class BinaryQuery: public Query_base { protected:
    BinaryQuery(const Query &l, const Query &r, std::string s): lhs(l), rhs(r), opSym(s) { }
    // абстрактный класс: BinaryQuery функцию eval() не определяет std::string rep() const { return "(" + lhs.rep() + " "
    + opSym + " "
    + rhs.rep() + ")"; }
    Query lhs, rhs; // правый и левый операнды std::string opSym; // имя оператора
    };
    Данными класса BinaryQuery являются два операнда запроса и символ оператора.
    Конструктор получает эти два операнда и символ оператора, каждый из которых он хранит в соответствующих переменных-членах.
    Чтобы отобразить объект класса BinaryOperator, следует вывести выражение в скобках,
    состоящее из левого операнда, оператора и правого операнда. Как и в случае класса
    NotQuery, вызов функции rep() в конечном счете осуществляет вызов виртуальных функций rep() объектов класса Query_base, на которые указывают параметры lhs и rhs.
    Класс BinaryQuery не переопределяет функцию eval(), а следовательно, наследует ее чистой виртуальной. Таким образом, класс BinaryQuery остается абстрактным и его объекты создавать нельзя. Классы AndQuery, OrQuery и их операторы
    Классы AndQuery и OrQuery, а также соответствующие им операторы очень похожи:
    Page 806/1103
    class AndQuery: public BinaryQuery { friend Query operators(const Query&, const Query&);
    AndQuery(const Query &left, const Query &right):
    BinaryQuery(left, right, "&") { }
    // конкретный класс: AndQuery наследует функцию rep(),
    // а остальные чистые виртуальные функции переопределяет
    QueryResult eval(const TextQuery&) const;
    }; inline Query operator&(const Query &lhs, const Query &rhs) { return std::shared_ptr<Query_base>(new AndQuery(lhs, rhs));
    } class OrQuery: public BinaryQuery { friend Query operator|(const Query&, const Query&);
    OrQuery(const Query &left, const Query &right):
    BinaryQuery(left, right, "|") { }
    QueryResult eval(const TextQuery&) const;
    }; inline Query operator|(const Query &lhs, const Query &rhs) { return std::shared_ptr<Query_base>(new OrQuery(lhs, rhs));
    }
    Эти классы объявляют соответствующий оператор дружественным и определяют конструктор, создающий их базовую часть класса BinaryQuery с соответствующим оператором. Они наследуют определение функции rep() от класса BinaryQuery, но каждый из них определяет собственную версию функции eval().
    Как и оператор операторы & и | возвращают указатель shared_ptr на вновь созданный объект соответствующего типа. Этот указатель shared_ptr приводится к типу Query в операторе return каждого из этих операторов. Упражнения раздела 15.9.3
    Упражнение 15.34. Исходя из выражения, представленного на рис. 15.3:
    (a) Перечислите конструкторы, задействованные при обработке этого выражения;
    Page 807/1103

    (b) Перечислите обращения к функции rep() из выражения cout << q;
    (c) Перечислите обращения к функции eval() из выражения q.eval.
    Упражнение 15.35. Реализуйте классы Query и Query_base, включая определение функции rep(), но исключая определение функции eval().
    Упражнение 15.36. Добавьте операторы вывода в конструкторы и функции-члены rep().
    Запустите код на выполнение, чтобы проверить свои ответы на вопросы (а) и (b) первого упражнения.
    Упражнение 15.37. Какие изменения следовало бы внести в классы, если бы у производных классов были члены типа shared_ptr<Query_base>, а не типа Query?

    Упражнение 15.38. Допустимы ли следующие объявления? Если нет, то почему? Если да, то что они означают?
    BinaryQuery а = Query("fiery") & Query("bird");
    AndQuery b = Query("fiery") & Query("bird");
    OrQuery с = Query("fiery") & Query("bird");
    15.9.4. Виртуальные функции eval()
    Функции eval() — основа системы запросов. Каждая из них вызывает функцию eval() своего операнда (операндов), а затем применяет собственную логику вычислений: функция eval()
    класса OrQuery возвращает объединение результатов своих операндов, а функция eval()
    класса AndQuery возвращает их пересечение. Функция eval() класса NotQuery немного сложней: она должна возвращать номера строк, не входящих в набор операнда.
    Для обеспечения обработки в функциях eval() необходимо использовать ту версию класса
    QueryResult, в который определены члены, добавленные в упражнениях раздела 12.3.2.
    Подразумевается, что у класса QueryResult есть функции-члены begin() и end(), позволяющие перебрать набор номеров строк, которые содержит объект класса QueryResult.
    Подразумевается также, что у класса QueryResult есть функция-член get_file(),
    возвращающая указатель shared_ptr на файл, к которому осуществляется запрос.
    Класс Query использует функции-члены begin() и end(), определенные для класса QueryResult в упражнении 12.3.2. Функция OrQuery::eval()
    Функция eval() класса OrQuery объединяет наборы номеров строк, возвращенных его операндами, т.е. ее результатом является объединение результатов двух операндов.
    Объект класса OrQuery представляет объединение результатов двух своих операндов,
    полученных при вызове функции-члена eval() каждого из них. Поскольку эти операнды являются объектами класса Query, вызов функции eval() является вызовом Query::eval(),
    который в свою очередь осуществляет виртуальный вызов функции eval() объекта базового класса Query_base. Каждый из этих вызовов возвращает объект класса QueryResult,
    представляющий номера строк, в которых присутствует его операнд. Эти номера строк объединяются в новый набор:
    //
    Page 808/1103
    возвращает объединение наборов результатов своих операндов
    QueryResult
    OrQuery::eval(const TextQuery& text) const {
    // виртуальные вызовы через члены Query, lhs и rhs
    // вызовы eval() возвращают QueryResult для каждого операнда auto right = rhs.eval(text), left = lhs.eval(text);
    // копировать номера строк левого операнда в результирующий набор auto ret_lines = make_shared<set<line_no>>(left.begin(), left.end());
    // вставить строки из правого операнда ret_lines->insert(right.begin(), right.end());
    // возвратить новый QueryResult, представляющий объединение lhs и rhs return QueryResult(rep(), ret_lines, left.get_file());
    }
    Набор ret_lines инициализируется с использования того конструктора, который получает пару итераторов. Функции-члены begin() и end() класса QueryResult возвращают итераторы на номера строк набора. Таким образом, набор ret_lines создается при копировании элементов из набора left. Затем для вставки элементов из набора right вызывается функция insert().
    После этого вызова набор ret_lines содержит номера строк из наборов, которые присутствуют в наборах left или right.
    Функция eval() завершает работу, создавая и возвращая объект класса QueryResult,
    представляющий объединение соответствий. Конструктор QueryResult() (см. раздел 12.3.2)
    получает три аргумента: строку, представляющую запрос, указатель shared_ptr на набор соответствующих номеров строк и указатель shared_ptr на вектор, представляющий входной файл. Вызов функции rep() позволяет создать строку, а вызов функции get_file() — получить указатель shared_ptr на файл. Поскольку оба набора, left и right, относятся к тому же файлу,
    не имеет значения, который из них использовать для функции get_file(). Функция
    AndQuery::eval()
    Версия функции eval() класса AndQuery подобна версии класса OrQuery, за исключением того, что она использует библиотечный алгоритм для поиска строк, общих для обоих запросов:
    Page 809/1103

    // возвращает пересечение наборов результатов своих операндов
    QueryResult
    AndQuery::eval(const TextQuery& text) const {
    // виртуальный вызов через операнды класса Query для получения
    // результирующих наборов для операндов auto left = lhs.eval(text), right = rhs.eval(text);
    // набор для хранения пересечения left и right auto ret_lines = make_shared<set<line_no>>();
    // выводит пересечение двух диапазонов в итератор назначения
    // итератор назначения в этом вызове добавляет элементы в ret set_intersection(left.begin(), left.end(), right.begin(), right.end(), inserter(*ret_lines, ret_lines->begin())); return QueryResult(rep(), ret_lines, left.get_file());
    }
    Здесь для объединения двух наборов используется библиотечный алгоритм set_intersection,
    описанный в приложении А.2.8.
    Алгоритм set_intersection получает пять итераторов. Первые четыре он использует для обозначения двух исходных последовательностей (см. раздел 10.5.2). Его последний аргумент обозначает получателя. Алгоритм выводит элементы, присутствующие в обеих исходных последовательностях, в результирующую.
    В данном вызове получателем является итератор вставки (см. раздел 10.4.1). Результатом записи алгоритмом set_intersection в этот итератор будет вставка нового элемента в набор ret_lines.
    Подобно функции eval() класса OrQuery, эта завершается созданием и возвращением объекта класса QueryResult, представляющего объединение соответствий. Функция
    NotQuery::eval()
    Функция eval() класса NotQuery ищет в тексте все строки, в которых операнд отсутствует.
    Page 810/1103

    // возвращает строки, отсутствующие в наборе результатов
    // операнда QueryResult
    NotQuery::eval(const TextQuery& text) const {
    // виртуальный вызов для вычисления операнда Query auto result = query.eval(text);
    // начать с пустого результирующего набора данных auto ret_lines = make_shared<set<line_no>>();
    // следует перебрать строки, в которых присутствует операнд auto beg = result.begin(), end = result.end();
    // для каждой строки во входном файле, если она отсутствует
    // в result, добавить ее номер в ret_lines auto sz = result.get_file()->size(); for (size_t n = 0; n != sz; ++n) {
    // если не обработаны все строки в result
    // проверить присутствие этой строки if (beg == end || *beg != n) ret_lines->insert(n); // если нет в result, добавить строку else if (beg != end)
    ++beg; // в противном случае получить следующий номер строки
    Page 811/1103

    // в result, если она есть
    } return QueryResult(rep(), ret_lines, result.get_file());
    }
    Как и другие функции eval(), данная начинается с вызова функции eval() операнда объекта.
    Этот вызов возвращает объект класса QueryResult, содержащий номера строк, в которых присутствует операнд. Однако вернуть необходимо набор номеров строк, в которых операнд отсутствует. Как и в других функциях eval(), данная начинается с вызова функции eval()
    операнда объекта. Вызов возвращает объект класса QueryResult, содержащий номера строк,
    в которых операнд присутствует, но необходимы номера строки, на которых операнд отсутствует. Поэтому следует найти в файле все строки, отсутствующие в наборе результатов.
    Набор создается в результате последовательного перебора целых чисел до размера входного файла. Каждое число, отсутствующее в наборе result, помещается в набор ret_lines.
    Итераторы beg и end устанавливаются на первый и следующий после последнего элементы в наборе result. Поскольку речь идет о наборе, при переборе номера строк будут следовать в порядке возрастания.
    Тело цикла проверяет наличие текущего числа в наборе result. Если его нет, то число добавляется в набор ret_lines. Если он есть, осуществляется приращение итератора beg набора result.
    Как только все номера строк будут обработаны, возвращается объект класса QueryResult,
    содержащий набор ret_lines наряду с результатами выполнения функций rep() и get_file(), как и у предыдущих функций eval(). Упражнения раздела 15.9.4
    Упражнение 15.39. Реализуйте классы Query и Query_base. Проверьте приложение на вычислении и выводе запроса, представленного на рис. 15.3.
    Упражнение 15.40. Что будет, если параметр rhs функции-члена eval() класса OrQuery возвратит пустой набор? Что, если так поступит ее параметр lhs? Что если и rhs, и lhs возвратят пустые множества?
    Упражнение 15.41. Переделайте свои классы так, чтобы использовать встроенные указатели на класс Query_base, а не интеллектуальные указатели shared_ptr. Помните, что ваши классы больше не смогут использовать синтезируемые функции-члены управления копированием.
    Упражнение 15.42. Разработайте и реализуйте одно из следующих дополнений.
    (a) Организуйте вывод слов только однажды в предложении, а не однажды в строке.
    (b) Снабдите систему историей, позволяющей пользователю обратиться к предыдущему запросу по номеру, а также добавлять или комбинировать их с другими.
    (c) Позвольте пользователю ограничивать результаты так, чтобы отображался набор соответствий только в заданном диапазоне строк.
    Резюме
    Page 812/1103

    Наследование позволяет создавать новые классы, которые совместно используют возможности их базового класса (классов), но при необходимости могут их переопределить или дополнить. Динамическое связывание позволяет компилятору во время выполнения выбрать версию применяемой функции на основании динамического типа объекта.
    Комбинация наследования и динамического связывания позволяет создавать программы,
    которые либо не зависят от типа объекта, либо имеют поведение, зависящие от типа объекта.
    В языке С++ динамическое связывание применимо только к тем функциям, которые объявлены виртуальными и вызываются при помощи ссылок или указателей.
    Объекты производных классов состоят из части (частей) базового класса и части производного. Поскольку частью объекта производного класса является объект базового,
    ссылку или указатель на объект производного класса вполне можно преобразовать в ссылку или указатель на его доступный базовый класс.
    При создании, копировании, перемещении и присвоении объектов производного класса сначала создается, копируется, перемещается и присваивается базовая часть объекта.
    Деструкторы выполняются в обратном порядке: сначала удаляется производная часть, затем выполняются деструкторы частей базовых классов. Базовые классы обычно определяют виртуальный деструктор, даже если у них нет никакой потребности в деструкторе.
    Производный класс определяет уровень защиты для каждого из своих базовых классов.
    Члены открытого базового класса являются частью интерфейса производного класса; члены закрытого базового класса недоступны; члены защищенного базового класса доступны для классов, производных от него, но не для пользователей производного класса.
    Термины
    Абстрактный класс (abstract base class). Класс, обладающий одной или несколькими чистыми виртуальными функциями. Нельзя создать объекты типа абстрактного базового класса.
    Базовый класс (base class). Класс, от которого происходит другой класс. Члены базового класса становятся членами производного класса.
    Виртуальная функция (virtual function). Функция-член, обеспечивающая зависимое от типа поведение. Во время выполнения выбор конкретной версии функции при обращении к виртуальной функции с помощью ссылки или указателя осуществляется на основании типа объекта, с которым связана ссылка или указатель.
    Динамический тип (dynamic type). Тип объекта во время выполнения. Динамический тип объекта, на который ссылается ссылка или указывает указатель, может отличаться от статического типа ссылки или указателя. Указатель или ссылка на тип базового класса может применяться к объекту производного типа. В таких случаях статическим типом будет ссылка
    (или указатель) на базовый класс, а динамическим — ссылка (или указатель) на производный.
    Динамическое связывание (dynamic binding). Отсрочка выбора выполняемой функции до времени выполнения. В языке С++ динамическим связыванием называют выбор во время
    Page 813/1103
    выполнения используемой версии виртуальной функции на основании фактического типа объекта, который связан со ссылкой или с указателем.
    Доступность (accessible). Член базового класса доступен через производный объект.
    Доступность зависит от спецификатора доступа, используемого в списке наследования производного класса, и уровня доступа члена в базовом классе. Например, открытый (public)
    член класса, унаследованный при открытом наследовании, доступен для пользователей производного класса. Открытый член базового класса недоступен, если наследование является закрытым.
    Закрытое наследование (private inheritance). При закрытом наследовании открытые и защищенные члены базового класса становятся закрытыми членами производного.
    Защищенное наследование (protected inheritance). При защищенном наследовании защищенные и открытые члены базового класса становятся защищенными членами производного.
    Косвенный базовый класс (indirect base class). Базовый класс, отсутствующий в списке наследования производного класса. Класс, от которого наследуется прямой базовый класс,
    прямо или косвенно является косвенным базовым классом для производного класса.
    Наследование (inheritance). Программная технология определения нового класса
    (производного) в терминах существующего класса (базового). Производный класс наследует члены базового класса.
    Объектно-ориентированное программирование (object-oriented programming). Техника программирования с использованием абстракции данных, наследования и динамического связывания.
    Открытое наследование (public inheritance). Открытый интерфейс базового класса является частью открытого интерфейса производного класса.
    Отсечение (sliced down). Происходящее при использовании объекта производного типа для инициализации или присвоения объекта базового типа. Производная часть объекта отсекается, оставляя только базовую часть, которая и присваивается объекту базового типа.
    Переопределение (override). Виртуальная функция, определенная в производном классе, с тем же списком параметров, что и у виртуальной функции в базовом классе, переопределяет определение базового класса.
    Полиморфизм (polymorphism). Применительно к объектно-ориентированному программированию — возможность получить специфическое для типа поведение на основании динамического типа ссылки или указателя.
    Преобразование производного класса в базовый (derived-to-base conversion). Неявное преобразование объекта производного класса в ссылку на базовый класс или указателя на объект производного класса в указатель на базовый класс.
    Привязка во время выполнения (run-time binding).
    См . динамическое связывание.
    Производный класс (derived class). Класс, унаследованный от другого класса. Производный класс может переопределить виртуальные функции своего базового класса и определять новые члены. Область видимости производного класса вкладывается в область ее базового класса (классов); члены производного класса могут использовать члены базового класса непосредственно.
    Page 814/1103

    Прямой базовый класс (direct base class). Базовый класс, от которого непосредственно происходит производный. Прямые базовые классы определяются в списке наследования производного класса. Прямой базовый класс сам может быть производным классом.
    Рефакторинг (refactoring). Способ перепроектирования программ, позволяющий собрать взаимосвязанные части в единую абстракцию при замене первоначального кода новой абстракцией. Рефакторинг классов, как правило, применяют для перемещения переменных или функций-членов в самый верхний общий пункт иерархии во избежание дублирования кода.
    Спецификатор доступа protected. К членам, определенным после ключевого слова protected,
    могут обращаться только члены производного класса и друзья. Однако доступны эти члены только через производные объекты. Защищенные члены не доступны для обычных пользователей класса.
    Список наследования класса (class derivation list). Список базовых классов, от которых происходит производный класс; у каждого из них может быть необязательный уровень доступа. Если спецификатора доступа нет, наследование открытое (public), если производный класс определен с ключевым словом struct, и закрытое (private), если класс определен с ключевым словом class.
    Статический тип (static type). Тип, с которым определяется переменная или возвращает выражение. Статический тип известен во время компиляции.
    Чистая виртуальная функция (pure virtual). Виртуальная функция, объявленная в заголовке класса с использованием = 0 в конце списка параметров функции. Чистая виртуальная функция не обязана (но вполне может) быть определена классом. Класс с чистой виртуальной функцией является абстрактным. Если производный класс не определяет собственную версию унаследованной чистой виртуальной функции, он также становится абстрактным.
    Глава 16
    Шаблоны и обобщенное программирование
    И объектно-ориентированное, и обобщенное программирование имеют дело с типами,
    неизвестными на момент написания программы. Различие между ними в том, что объектно-ориентированное программирование имеет дело с типами, которые не известны до времени выполнения, тогда как в обобщенном программировании типы становятся известны только во время компиляции.
    Все описанные в части II контейнеры, итераторы и алгоритмы являются хорошими примерами обобщенного программирования. При написании обобщенной программы ее код должен работать способом, независимым от специфических типов. При использовании обобщенного кода ему следует предоставить типы или значения, с которыми будет работать данный конкретный экземпляр кода.
    Например, библиотека предоставляет единое, обобщенное определение каждого контейнера,
    такого как вектор. Это обобщенное определение можно использовать для определения множества разных типов векторов, каждый из которых отличается от других типом хранимых элементов.
    Page 815/1103

    Шаблоны (template) — это основа обобщенного программирования. Шаблоны вполне можно использовать (как выше в книге), даже не понимая, как они определяются. В этой главе рассматривается определение собственных шаблонов.
    В языке С++ шаблоны являются основой для общего программирования. Шаблон — это проект или формула для создания класса или функции.
    При использовании такого обобщенного типа, как vector, или такой обобщенной функции, как find(), следует предоставить дополнительную информацию, необходимую для трансформации их проекта в конкретный класс или функцию во время компиляции.
    Использование шаблонов рассматривалось в главе 3, а в этой главе мы изучим их определение.
    16.1. Определение шаблона
    Предположим, необходимо написать функцию, которая сравнивает два значения и указывает,
    является ли первое из них меньшим, равным или большим, чем второе. Фактически придется создать несколько таких функций, каждая из которых сможет сравнивать значения определенного типа. На первом этапе можно было бы определить несколько перегруженных функций.
    // возвращает 0, если значения равны, -1, если v1 меньше, и 1,
    // если меньше v2 int compare(const string &v1, const string &v2) { if (v1 < v2) return -1; if (v2 < v1) return 1; return 0;
    } int compare(const double &v1, const double &v2) { if (v1 < v2) return -1; if (v2 < v1) return 1; return 0;
    }
    Эти функции почти идентичны и отличаются только типом параметров. Тела у обеих функций одинаковы.
    Повторение тела функции для каждого сравниваемого типа не только утомительно, но и повышает вероятность возникновения ошибок. Однако важней всего то, что в этом случае
    Page 816/1103
    необходимо заранее знать все типы, которые придется сравнивать. Этот подход не сработает в случае,
    когда функцию предполагается использовать для типов, неизвестных на данный момент.
    16.1.1. Шаблоны функций
    Вместо того чтобы определять новую функцию для каждого типа, мы можем определить шаблон функции (function template). Шаблон функции — это проект, по которому можно создать некую версию данной функции, специфическую для заданного типа. Шаблон функции compare() может выглядеть так: template <typename Т> int compare(const T &v1, const T &v2) { if (v1 < v2) return -1; if (v2 < v1) return 1; return 0;
    }
    Определение шаблона начинается с ключевого слова template, за которым следует разделяемый запятыми и заключенный в угловые скобки (<>) список параметров шаблона (template parameter list), один или несколько параметров шаблона (template parameter).
    Список параметров в определении шаблона не может быть пустым
    Список параметров шаблона очень похож на список параметров функции. Список параметров функции задает имена и типы локальных переменных, но оставляет их неинициализированными. Инициализацию параметров во время выполнения обеспечивают аргументы.
    Аналогично параметры шаблона представляют типы или значения, используемые при определении класса или функции. При использовании шаблона необходимо (явно или неявно) определить аргументы шаблона (template argument), чтобы связать их с соответствующими параметрами шаблона.
    Например, рассматриваемая функция compare() объявляет единственный параметр типа Т. В
    шаблоне compare имя Т можно использовать там, где должно быть название типа данных.
    Фактический тип Т будет определен компилятором на основании способа применения функции.Создание экземпляра шаблона функции
    Когда происходит вызов шаблона функции, для вывода типов аргументов шаблона компилятор обычно использует аргументы вызова. Таким образом, когда происходит вызов
    Page 817/1103
    шаблона compare, компилятор использует тип аргументов для определения типа, связанного с параметром шаблона Т. Рассмотрим следующий вызов: cout << compare(1, 0) << endl; //
    Т - тип int
    Здесь аргумент имеет тип int. Компилятор выведет и использует тип int как аргумент шаблона,
    а также свяжет этот аргумент с параметром Т шаблона.
    При создании экземпляра (instantiation) специфической версии функции компилятор сам использует выведенные параметры шаблона. При этом он подставляет фактические аргументы шаблона вместо соответствующих параметров шаблона. Рассмотрим следующий вызов:
    // создание экземпляра int compare(const int&, const int&) cout << compare(1, 0) << endl; //
    T - тип int
    // создание
    // экземпляра int compare(const vector<int>&, const vector<int>&) vector<int> vec1{1, 2, 3}, vec2{4, 5, 6}; cout << compare(vec1, vec2) << endl; //
    T - тип vector<int>
    Здесь компилятор создает два экземпляра разных версий функции compare(). В первой из них параметр Т заменен типом int. int compare(const int &v1, const int &v2) { if (v1 < v2) return -1; if (v2 < v1) return 1; return 0;
    }
    Во втором вызове создается версия функции compare() с параметром Т, замененным типом vector<int>. Такое создание компилятором функций обычно и называют созданием экземпляра шаблона.Параметры типа шаблона
    У функции compare() есть один
    Page 818/1103
    параметр типа (type parameter) шаблона. Как правило, параметр типа можно использовать как спецификатор типа таким же образом, как и встроенный спецификатор типа или класса. В
    частности, параметр типа применим при назначении типа возвращаемого значения или типа параметра функции, а также в объявлениях переменных или приведениях в теле функции:
    // ok: для возвращаемого значения и параметра используется тот же тип template <typename Т> Т foo(Т* p) {
    Т tmp = *p; // тип tmp совпадает с типом, на который указывает p
    // ... return tmp;
    }
    Каждому параметру типа должно предшествовать ключевое слово class или typename:
    // ошибка: U должно предшествовать либо typename, либо class template <typename Т, U> Т calc(const T&, const U&);
    В списке параметров шаблона эти ключевые слова имеют одинаковый смысл и применяются взаимозаменяемо. Оба ключевых слова применимы одновременно:
    // ok: в списке параметров шаблона нет никакой разницы между ключевыми
    // словами typename и class template <typename Т, class U> calc(const T&, const U&);
    Для обозначения параметра типа шаблона интуитивно понятней использовать ключевое слово typename, а не class; в конце концов, для фактического типа параметра вполне может быть использован встроенный тип, а не только класс. Кроме того, ключевое слово typename более точно указывает на то, что следующее за ним имя принадлежит типу. Однако ключевое слово typename было добавлено в язык С++ как часть стандарта С++, поэтому в устаревших программах, вероятнее всего, осталось исключительно ключевое слово class. Параметры значения шаблона
    Кроме параметров типа, в определении шаблона могут быть использованы параметры значения (nontype parameter). Параметр значения представляет значение, а не тип. При определении параметров значения вместо ключевого слова class или typename используются имена типов.
    При создании экземпляра шаблона такие параметры заменяются значением,
    предоставленным пользователем или выведенным компилятором. Чтобы компилятор смог
    Page 819/1103
    создать экземпляр шаблона во время компиляции, эти значения должны быть константными выражениями (см. раздел 2.4.4).
    В качестве примера напишем версию функции compare(), работающую со строковыми литералами. Такие литералы представляют собой массивы типа const char. Поскольку скопировать массив нельзя, определим параметры как ссылки на массив (раздел 6.2.4).
    Поскольку необходима возможность сравнивать литералы разных длин, снабдим шаблон двумя параметрами значения. Первый параметр шаблона представляет размер первого массива, а второй — размер второго: template<unsigned N, unsigned M> int compare(const char (&p1)[N], const char (&p2)[M]) { return strcmp(p1, p2);
    }
    При вызове следующей версии функции compare() компилятор будет использовать размер литералов для создания экземпляра шаблона с размерами, которыми заменяют параметры N
    и M: compare("hi", "mom")
    Не забывайте, что компилятор завершает строковый литерал пустым символом (см. раздел
    2.1.3). В результате компилятор создаст такой экземпляр: int compare(const char (&p1)[3], const char (&p2)[4])
    Параметр значения может быть целочисленным типом, указателем, ссылкой на объект
    (l-значением) или на тип функции. Аргумент, связанный с целочисленным параметром значения, должен быть константным выражением. У аргументов, привязанных к указателю или ссылочному параметру значения, должна быть статическая продолжительность существования (см. главу 12). Нельзя использовать обычный (нестатический) локальный или динамический объект как аргумент шаблона для параметра значения шаблона в виде ссылки или указателя. Параметр-указатель может быть также создан как nullptr или нулевое константное выражение.
    Параметр значения шаблона — это константное значение в определении шаблона. Параметр значения применим там, где требуются константные выражения, например, при определении размера массива.
    Аргументы шаблона, используемые для параметров значения, должны быть константными выражениями. Шаблоны функции со спецификаторами inline и constexpr
    Шаблон функции может быть объявлен как inline (встраиваемый) или constexpr, как и обычная функция. Спецификаторы inline и constexpr располагаются после списка параметров шаблона, но перед типом возвращаемого значения.
    // ok: спецификатор inline следует за списком параметров шаблона template <typename Т> inline Т min(const Т&, const Т&);
    //
    Page 820/1103
    ошибка: неправильное размещение спецификатора inline inline template <typename T> T min(const T&, const T&); Создание кода,
    независимого от типа
    Продемонстрируем два наиболее важных принципа создания обобщенного кода на примере функции compare().
    • Параметры функций в шаблоне должны быть ссылками на константу.
    • При проверке в теле шаблона следует использовать только оператор сравнения <.
    Объявление параметров функций ссылками на константы гарантирует возможность применения функции к типам, которые не допускают копирования. Большинство типов,
    включая встроенные типы, но исключая указатели unique_ptr и типы ввода-вывода, а также все использованные ранее библиотечные типы допускают копирование. Но вполне могут встретиться и другие типы, которые не допускают копирования. Сделав параметры ссылками на константы, можно гарантировать применимость таких типов в функции compare(). Кроме того, если функция compare() будет применена для больших объектов, такая конструкция позволит избежать копирования и сэкономит время при выполнении.
    Некоторые читатели могут подумать, что для сравнения было бы целесообразней использовать оба оператора < и >.
    // ожидаемое сравнение if (v1 < v2) return -1; if (v1 > v2) return 1; return 0;
    Однако написание кода, использующего только оператор <, снизит требования к типам,
    которые применимы в функции compare(). Эти типы должны поддерживать оператор <, но не обязаны поддерживать оператор >.
    Фактически, если действительно следует обеспечить независимость от типа и переносимость кода, лучше определить свою функцию, используя тип less (см. раздел 14.8.2):
    // версия функции compare(), корректно работающая даже с
    // указателями; см. p. 14.8.2 template <typename Т> int compare(const T &v1, const T &v2) { if (less<T>()(v1, v2)) return -1; if (less<T>()(v2, v1)) return 1; return 0;
    }
    Page 821/1103

    Проблема первоначальной версии в том, что если пользователь вызовет ее с двумя указателями, не указывающими на тот же массив, то результат выполнения кода будет непредсказуем.
    При написании кода шаблонов следует постараться минимизировать количество требований,
    накладываемых на типы аргументов. Компиляция шаблона
    Когда компилятор встречает определение шаблона, он не создает код. Код создается только при создании специфического экземпляра шаблона. Тот факт, что код создается только при использовании шаблона (а не при его определении), влияет как на организацию исходного кода, так и на способы обнаружения ошибок.
    Обычно, когда происходит вызов функции, компилятору достаточно объявления функции.
    Точно так же при использовании объекта класса должно быть доступно определение класса,
    но определения функций-членов не обязательны. В результате определения классов и объявления функций имеет смысл размещать в файлах заголовка, а определения обычных функций и функций-членов — в файлах исходного кода.
    С шаблонами все не так: для создания экземпляра у компилятора должен быть код,
    определяющий шаблон функции или функцию-член шаблона класса. В результате, в отличие от обычного кода, заголовки для шаблонов обычно включают определения наравне с объявлениями.
    Определения шаблонов функций и функций-членов шаблонов классов обычно помещаются в файлы заголовка. Ключевая концепция. Шаблоны и заголовки
    Шаблоны содержат два вида имен:
    • не зависящие от параметров шаблона;
    • зависящие от параметров шаблона.
    Именно разработчик шаблона гарантирует, что все имена, не зависящие от параметров шаблона, будут видимы на момент использования шаблона. Кроме того, разработчик шаблона должен гарантировать видимость определения шаблона, включая определения членов шаблона класса, на момент создания экземпляра шаблона.
    Пользователь шаблона должен обеспечить видимость объявлений всех функций, типов и связанных с ними операторов, используемых при создании экземпляра шаблона.
    Выполнение этих требований невозможно без хорошо организованной структуры программы,
    в которой заголовки используются соответствующим образом. Автор шаблона должен предоставить заголовок, который содержит объявления всех имен, используемых в шаблоне класса или в определениях его членов. Прежде чем создать экземпляр шаблона для определенного типа или использовать член класса, созданного по этому шаблону,
    пользователь должен подключить заголовок для типа шаблона и заголовок, в котором определен используемый тип. Ошибки компиляции проявляются, главным образом, во время создания экземпляра
    Тот факт, что код не создается до создания экземпляра шаблона, влияет на то, когда проявляются ошибки компиляции в коде шаблона. В процессе создания шаблона есть три этапа, во время которых компилятор может сообщить об ошибке.
    Первый — когда компилируется само определение шаблона. На этом этапе компилятор, как правило, не может найти большую часть ошибок. Здесь обнаруживаются в основном синтаксические ошибки, такие как пропущенная точка с запятой или неправильно написанное
    Page 822/1103
    имя переменной, но не более.
    Второй этап обнаружения ошибок — когда компилятор встречает применение шаблона. На данном этапе компилятор также способен проверить немногое. Для вызова шаблона функции компилятор обычно проверяя количество и типы аргументов. Он может также проверить совпадение типов двух аргументов. Для шаблона класса компилятор может проверить количество и правильность предоставленных аргументов шаблона, но не более.
    Третий этап обнаружения ошибок — момент создания экземпляра. Только теперь обнаруживаются ошибки, связанные с типами. В зависимости от того, как компилятор осуществляет создание экземпляра, он может сообщить об этих ошибках во время редактирования.
    При написании шаблона код не может быть открыто специфическим для типа, но можно сделать некоторые предположения об используемых типах. Например, код первоначальной функции compare() подразумевал, что тип аргумента имеет оператор <. if (v1 < v2) return -1; // для объектов типа Т требуется оператор < if (v2 < v1) return 1; // для объектов типа Т требуется оператор < return 0; // возвращает int; не зависит от Т
    Когда компилятор обрабатывает тело этого шаблона, он не может проверить корректность условий в операторах if. Если переданные функции compare() аргументы имеют оператор <,
    то код сработает прекрасно, но не в противном случае. Например:
    Sales_data data1, data2; cout << compare(data1, data2) << endl; // ошибка: у Sales_data нет
    // оператора <
    Этот вызов создает экземпляр функции compare() с параметром Т, замененным классом
    Sales_data. Если условия попытаются использовать оператор < для объектов класса
    Sales_data, то окажется, что такого оператора нет. В результате получится экземпляр функции, которая не будет откомпилирована. Такие ошибки, как эта, не могут быть обнаружены, пока компилятор не создаст экземпляр определения функции compare() для типа Sales_data.
    Вызывающая сторона должна гарантировать, что переданные шаблону аргументы поддерживают все используемые им операторы, а также то, что эти операторы будут вести себя правильно в том контексте, в котором шаблон использует их. Упражнения раздела 16.1.1
    Упражнение 16.1. Определите создание экземпляра.
    Упражнение 16.2. Напишите и проверьте собственные версии функций compare().
    Page 823/1103

    Упражнение 16.3. Вызовите собственную функцию compare() для объекта класса Sales_data и посмотрите, как ваш компилятор обрабатывает ошибки во время создания экземпляра.
    Упражнение 16.4. Напишите шаблон, действующий как библиотечный алгоритм find().
    Функция будет нуждаться в двух параметрах типа шаблона: один — для представления параметров-итераторов функции и другой — для типа значения. Используйте свою функцию для поиска заданного значение в векторе vector<int> и списке list<string>.
    Упражнение 16.5. Напишите шаблон функции print() из раздела 6.2.4, которая получает ссылку на массив и может обрабатывать массивы любого размера и любого типа элементов.
    Упражнение 16.6. Как работают библиотечные функции begin() и end(), получающие аргумент в виде массива? Определите собственные версии этих функций.
    Упражнение 16.7. Напишите шаблон constexpr, возвращающий размер заданного массива.
    Упражнение 16.8. В разделе "Ключевая концепция" в разделе 3.4.1 упоминалось о том, что программисты С++ привыкли использовать оператор !=, а не <. Объясните причину этой привычки.
    16.1.2. Шаблоны класса
    Шаблон класса (class template) — своего рода проект для создания классов. Шаблоны классов отличаются от шаблонов функций, для которых компилятор не может вывести типы параметров шаблона. Вместо этого, как уже демонстрировалось не раз, для использования шаблона класса следует предоставить дополнительную информацию в угловых скобках после имени шаблона (см. раздел 3.3). Эта дополнительная информация — список аргументов шаблона, подставляемых вместо параметров шаблона.Определение шаблона класса
    В качестве примера реализуем шаблонную версию класса StrBlob (см. раздел 12.1.1).
    Присвоим шаблону имя Blob, указывающее, что он больше не специфичен только для строк.
    Как и класс StrBlob, этот шаблон будет предоставлять совместный (и проверяемый) доступ к своим членам. В отличие от класса, шаблон применяется к элементам практически любого типа. Подобно библиотечным контейнерам, используя шаблон Blob, пользователи должны будут определить тип элемента.
    Как и шаблоны функции, шаблоны класса начинаются с ключевого слова template, за которым следует список параметров шаблона. В определении шаблона класса (и его членов)
    используются параметры шаблона как знакоместа типов или значений, которые будут подставлены при использовании шаблона: template <typename Т> class Blob { public: typedef T value_type; typedef typename std::vector<T>::size_type size_type;
    // конструкторы
    Page 824/1103

    Blob();
    Blob(std::initializer_list<T> il);
    // количество элементов в Blob size_type size() const { return data->size(); } bool empty() const { return data->empty(); }
    // добавление и удаление элементов void push_back(const T &t) {data->push_back(t);}
    // версия перемещения; см. p. 13.6.3 void push_back(T &&t) { data->push_back(std::move(t)); } void pop_back();
    // доступ к элементу
    T& back();
    Т& operator[](size_type i); // определено в разделе 14.5 private: std::shared_ptr<std::vector<T>> data;
    // выдать сообщение, если data[i] недопустим void check(size_type i, const std::string &msg) const;
    }
    У шаблона Blob есть один параметр типа Т. Он используется везде, где ожидается тип элемента, хранимый классом Blob. Например, тип возвращаемого значения функции доступа к элементам Blob определен как Т&. Когда пользователь создаст экземпляр шаблона
    Blob, он использует параметр Т для замены конкретным типом аргумента шаблона.
    За исключением списка параметров шаблона и использования Т вместо string, этот класс совпадает с тем, что было определено в разделе 12.1.1 и модифицировано в разделе 12.1.6,
    а также в главах 13 и 14. Создание экземпляра шаблона класса
    Как уже неоднократно упоминалось, при использовании шаблона класса следует предоставить дополнительную информацию. Как можно теперь утверждать, эта
    Page 825/1103
    дополнительная информация является списком явных аргументов шаблона (explicit template argument), которые привязаны к параметрам шаблона. Компилятор использует эти аргументы для создания специфического экземпляра класса по шаблону.
    Например, чтобы определить тип для шаблона Blob, следует предоставить тип элемента:
    Blob<int> ia; // пустой Blob<int>
    Blob<int> ia2 = {0,1,2,3,4}; // Blob<int> с пятью элементами
    Оба объекта, ia и ia2, используют ту же специфическую для типа версию шаблона Blob (т.е.
    Blob<int>). Из этих определений компилятор создает экземпляр класса, который эквивалентен следующему: template <> class Blob<int> { typedef typename std::vector<int>::size_type size_type;
    Blob();
    Blob(std::initializer_list<int> il);
    // ... int& operator[](size_type i); private: std::shared_ptr<std::vector<int>> data; void check (size_type i, const std::string &msg) const;
    };
    Когда компилятор создает экземпляр класса из шаблона Blob, он переписывает его, заменяя каждый экземпляр параметра T заданным аргументом шаблона, которым в данном случае является int.
    Компилятор создает разный класс для каждого заданного типа элемента:
    // эти определения создают экземпляр двух разных типов Blob
    Blob<string> names; //
    Blob содержащий строки
    Blob<double> prices; // другой тип элемента
    Эти определения привели бы к созданию двух разных экземпляров класса: определение names создает класс Blob, в котором каждое вхождение Т заменено на string. Определение prices создает класс Blob, где Т заменено на double.
    При каждом создании экземпляра шаблона класса получается независимый класс. У типа
    Page 826/1103

    Blob<string> нет никаких отношений с другим типом класса Blob или специальных прав доступа к его членам. Ссылки на тип шаблона в пределах шаблона
    При чтении кода шаблона класса не следует забывать, что имя шаблона класса не является именем самого класса (см. раздел 3.3). Шаблон класса используется для создания экземпляра класса, при этом всегда используются аргументы шаблона.
    Непонятным может показаться то, что код в шаблоне класса вообще не использует имя фактического типа (или значения) как аргумент шаблона. Вместо этого как аргументы шаблона зачастую используются собственные параметры. Например, переменная-член data использует два шаблона, vector и shared_ptr. Каждый раз, когда используется шаблон,
    следует предоставить аргументы шаблона. В данном случае предоставляемый аргумент шаблона имеет тот же тип, который используется при создании экземпляра шаблона Blob.
    Следовательно, определение переменной-члена data с использованием параметра типа шаблона Blob свидетельствует о том, что переменная-член data является экземпляром указателя shared_ptr на экземпляр шаблона vector, содержащего объекты типа Т. std::shared_ptr<std::vector<T>> data;
    При создании экземпляра специфического класса Blob, такого как Blob<string>,
    переменная-член data будет такой: shared_ptr<vector<string>>
    Если создать экземпляр Blob<int>, то переменная-член data будет такой:
    shared_ptr<vector<int>>, и т.д. Функции-члены шаблонов класса
    Подобно любому классу, функции-члены шаблона класса можно определить как в, так и вне тела класса. Как и у любых других классов, члены, определенные в теле, неявно являются встраиваемыми.
    Функция-член шаблона класса сама по себе является обычной функцией. Однако у каждого экземпляра шаблона класса есть собственная версия каждого члена. В результате у функции-члена шаблона класса будут те же параметры шаблона, что и у самого класса.
    Поэтому функция-член, определенная вне тела шаблона класса, начинается с ключевого слова template, сопровождаемого списком параметров шаблона класса.
    Как обычно, при определении члена класса вне его тела следует указать, к какому классу он принадлежит. Так же, как обычно, имя созданного из шаблона класса включает его аргументы шаблона. При определении члена аргументы шаблона совпадают с параметрами шаблона.
    Таким образом, для функции-члена класса StrBlob, определенной следующим образом: тип_возвращаемого_значения StrBlob:: имя_члена ( список_парам ) соответствующий член шаблона Blob будет выглядеть так: template <typename Т> тип_возвращаемого_значения Blob<Т>:: имя_члена ( список_парам )Функция check() и функции доступа к членам
    Page 827/1103

    Начнем с определения функции-члена 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();
    }
    Page 828/1103

    Оператор индексирования и функция-член 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)
    Page 829/1103

    Этот код создает экземпляр класса 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 на вектор
    Page 830/1103
    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; // перемещение на один элемент; префиксный ++
    //
    Page 831/1103
    проверяет инкремент return ret; // возвратить сохраненное состояние
    }
    Поскольку тип возвращаемого значения присутствует вне области видимости класса, следует указать, что он возвращает экземпляр BlobPtr, созданный с тем же типом, что и класс. В теле функции код находится в пределах класса, поэтому не нужно повторять аргумент шаблона при определении ret. Когда аргументы шаблона не предоставлены, компилятор подразумевает, что используется тот же тип, что и при создании экземпляра функции-члена.
    Следовательно, определение ret будет эквивалентно следующему:
    BlobPtr<T> ret = *this;
    В области видимости шаблона класса можно обращаться к шаблону, не определяя его аргументы. Шаблоны классов и дружественные отношения
    Когда класс объявляет дружественные отношения (см. раздел 7.2.1), класс и его друг могут быть или не быть шаблонами. Шаблон класса, у которого есть друг, не являющийся шаблоном, предоставляет дружественный доступ ко всем экземплярам шаблона. Когда друг сам является шаблоном, предоставляющий дружественные отношения класс контролирует,
    распространяются ли они на все экземпляры шаблона или только на некий специфический экземпляр. Дружественные отношения "один к одному"
    Наиболее распространенная форма дружественных отношений между шаблоном класса и другим шаблоном (класса или функции) подразумевает дружбу между соответствующими экземплярами класса и его друга. Например, класс Blob должен объявить дружественным класс BlobPtr и шаблонную версию оператора равенства класса Blob (первоначально определенную для класса StrBlob в упражнении раздела 14.3.1).
    Чтобы обратиться к определенному экземпляру шаблона (класса или функции), следует сначала объявить сам шаблон. Объявление шаблона включает список параметров шаблона:
    // для объявления дружественных отношений в шаблоне Blob нужны
    // предварительные объявления template <typename> class BlobPtr; template <typename> class Blob; // необходимо для параметров operator== template <typename T> bool operator==(const Blob<T>&, const Blob<T>&); template <typename T> class Blob {
    //
    Page 832/1103
    каждый экземпляр Blob предоставляет доступ к версии BlobPtr и
    // оператору равенства экземпляра, созданного с тем же типом friend class BlobPtr<T>; friend bool operator==<T>
    (const Blob<T>&, const Blob<T>&);
    // другие члены, как в разделе 12.1.1
    };
    Начнем с объявления Blob, BlobPtr и operator== шаблонами. Эти объявления необходимы для объявления параметра в функции operator== и дружественных объявлений в шаблоне Blob.
    Объявления дружественными используют параметр шаблона Blob как собственный аргумент шаблона. Таким образом, дружба ограничивается этими экземплярами шаблона BlobPtr и оператора равенства, которые создаются с тем же типом:
    Blob<char> ca; //
    BlobPtr<char> и operator==<char> друзья
    Blob<int> ia; //
    BlobPtr<int> и operator==<int> друзья
    Члены класса BlobPtr<char> могут обращаться к не открытым членам объекта ca (или любого другого объекта класса Blob<char>), но объект ca не имеет никаких специальных прав доступа к объекту ia (или любому другому объекту класса Blob<int>) или любому другому экземпляру класса Blob. Общие и специфические дружественные отношения шаблонов
    Класс может также сделать дружественным каждый экземпляр шаблона или ограничить дружбу специфическим экземпляром:
    // предварительное объявление необходимо для объявления дружественных
    // отношений со специфическим экземпляром шаблона template <typename Т> class Pal; class С { //
    С - обычный, не шаблонный класс friend class Pal<C>; // экземпляр Pal создается с классом С как
    Page 833/1103

    // дружественным
    // все экземпляры Раl2 дружественны С;
    // при предоставлении дружественных отношений всем экземплярам
    // предварительное объявление не обязательно template <typename Т> friend class Раl2;
    }; template <typename T> class C2 { //
    C2 - сам шаблон класса
    // у каждого экземпляра C2 есть тот же экземпляр Pal, что и у друга friend class Pal<T>; // объявление шаблона для Pal должно быть в
    // области видимости
    // все экземпляры Раl2 - друзья каждого экземпляра C2; необходимо
    // предварительное объявление template <typename X> friend class Раl2;
    //
    Pal3 - не шаблонный класс, являющийся другом каждого экземпляра C2 friend class Раl3; // предварительное объявление для Раl3
    // не обязательно
    };
    Page 834/1103

    Чтобы позволить создавать все экземпляры как дружественные, объявление дружественных отношений должно использовать параметры шаблона, которые отличаются от используемых самим классом. Объявление параметра типа шаблона дружественным
    По новому стандарту параметр типа шаблона можно сделать дружественным: template <typename Type> class Bar { friend Type; // предоставить доступ к типу, используемому для создания
    // экземпляра Bar
    // ...
    };
    Здесь указано, что, независимо от используемого для создания экземпляра типа, класс Bar будет дружественным. Таким образом, для некоего типа под названием Foo он был бы другом для Bar<Foo>, а тип Sales_data — другом для Bar<Sales_data> и т.д.
    Следует заметить, что хотя другом обычно бывает класс или функция, для класса Bar вполне допустимо создание экземпляра со встроенным типом. Такие дружественные отношения позволяют создавать экземпляры таких классов, как Bar со встроенными типами. Псевдонимы типа шаблона
    Экземпляр шаблона класса определяет тип класса, и, подобно любому другому типу класса,
    для экземпляра класса можно определить псевдоним при помощи ключевого слова typedef
    (см. раздел 2.5.1): typedef Blob<string> StrBlob;
    Это определение типа позволит выполнить код, написанный в разделе 12.1.1, используя текущую версию шаблона Blob, экземпляр которого создан для типа string. Поскольку шаблон не тип, ключевое слово typedef к шаблону неприменимо. Таким образом, нет никакого способа определить typedef для шаблона Blob<Т>.
    Однако новый стандарт позволяет определять псевдоним типа для шаблона класса: template<typename Т> using twin = pair<T, Т>; twin<string> authors; // authors - это pair<string, string> где имя twin определено как синоним для пар с одинаковыми типами членов. Пользователям типа twin достаточно определить его только однажды.
    Псевдоним типа шаблона — это синоним для целого семейства классов: twin<int> win_loss; // win_loss - это pair<int, int> twin<double> area; //
    Page 835/1103
    area - это pair<double, double>
    Как и при использовании шаблона класса, при использовании псевдонима twin следует указать, какой именно вид twin необходим.
    При определении псевдонима типа шаблона можно зафиксировать один или несколько параметров шаблона: template <typename Т> using partNo = pair<T, unsigned>; partNo<string> books; // books - это pair<string, unsigned> partNo<Vehicle> cars; // cars - это pair<Vehicle, unsigned> partNo<Student> kids; // kids - это pair<Student, unsigned>
    Здесь имя partNo определено как синоним семейства типов, которые являются парами,
    вторая переменная-член которого имеет тип unsigned. Пользователи partNo определяют тип первой переменной-члена пары, но не второй. Статические члены шаблонов класса
    Подобно любому другому классу, шаблон класса способен объявить статические члены (см.
    раздел 7.6): template <typename T> class Foo { public: static std::size_t count() { return ctr; }
    // другие члены интерфейса private: static std::size_t ctr;
    // другие члены реализации
    }; где Foo — шаблон класса, у которого есть открытая статическая функция-член count() и закрытая статическая переменная-член ctr. У каждого экземпляра шаблона Foo будет собственный экземпляр статических членов. Таким образом, для любого конкретного типа X
    будет по одной переменной Foo<X>::ctr и одной функции Foo<X>::count(). Все объекты типа Foo<X> будут совместно использовать ту же переименую ctr и функцию count(). Например:
    // создает экземпляр статических членов Foo<string>::ctr
    Page 836/1103

    // и Foo<string>::count
    Foo<string> fs;
    // все три объекта совместно используют те же члены Foo<int>::ctr
    // и Foo<int>::count
    Foo<int> fi, fi2, fi3;
    Подобно любой другой статической переменной-члену, у каждой статической переменной-члена шаблона класса должно быть только одно определение. Однако для каждого экземпляра шаблона класса будет отдельный объект. В результате статическую переменную-член шаблона определяют таким же образом, как и функции-члены этого шаблона: template <typename Т> size_t Foo<T>::ctr = 0; // определение и инициализация ctr
    Подобно любым другим членам шаблона класса, начнем с определения списка параметров шаблона, сопровождаемого типом и именем определяемого члена. Как обычно, имя члена включает имя класса, которое включает для класса, созданного из шаблона, его аргументы шаблона. Таким образом, когда класс Foo создается как экземпляр для специфического типа аргумента шаблона, для этого класса будет создан отдельный экземпляр переменной ctr и инициализирован значением 0.
    Подобно статическим членам обычного класса, к статическому члену шаблона класса можно обратиться через объект класса или непосредственно, при помощи оператора области видимости. Конечно, чтобы использовать статический член через класс, следует обратиться к его конкретному экземпляру:
    Foo<int> fi; // создает экземпляр класса Foo<int>
    // и статической переменной-члена ctr auto ct = Foo<int>::count(); // создает экземпляр Foo<int>::count() ct = fi.count(); // использует Foo<int>::count() ct = Foo::count(); //
    Page 837/1103
    ошибка: экземпляр какого именно

    1   ...   39   40   41   42   43   44   45   46   ...   54


    написать администратору сайта