Даже если вы предпочтете воспользоваться во внешнем интерфейсе модуля низкоуровневой абстракцией, всегда используйте во внутренней реализации абстракции максимально высокого уровня и преобразуйте их в низкоуровневые абстракции на границах модуля. Например, если у вас имеются клиенты, не использующие С++, вы можете воспользоваться непрозрачным указателем void*
или дескриптором типа int
для работы с клиентом, но во внутренней реализации используйте высокоуровневые объекты. Преобразование между этими объектами и выбранными низкоуровневыми типами выполняйте только в интерфейсе модуля.
std::string Translate(const std::string&);
Для библиотек, используемых внутри одной команды компании, это обычно неплохое решение. Но если вы планируете динамически компоновать данный модуль с вызывающим кодом, который использует иную реализацию std::string
(например, иное размещение в памяти), то из-за такого несоответствия могут случиться разные странные и неприятные вещи.
Мы встречались с разработчиками, которые пытались использовать собственный класс-оболочку CustomString
для объектов std::string
, но в результате они сталкивались с той же проблемой, поскольку не имели полного контроля над процессом сборки всех клиентских приложений.
Одно из решений состоит в переходе к переносимым (вероятно, встроенным) типам, как вместо функции с аргументом string
, так и в дополнение к ней. Такой новой функцией может быть функция
void Translate(const char* src, char* dest, size_t destSize);
Использование низкоуровневой абстракции более переносимо, но всегда добавляет сложности; здесь, например, как вызывающий, так и вызываемый код должны явно использовать обрезку строки, если размера буфера оказывается недостаточно. (Заметим, что данная версия использует буфер, выделяемый вызывающим кодом, для того чтобы избежать ловушки, связанной с выделением и освобождением памяти в разных модулях — см. рекомендацию 60.)
Шаблоны и обобщенность
Место для вашей цитаты.
Аналогично: место для вашего введения.
В этом разделе мы считаем наиболее значимой рекомендацию 64 — "Разумно сочетайте статический и динамический полиморфизм".
64. Разумно сочетайте статический и динамический полиморфизм
Статический и динамический полиморфизм дополняют друг друга. Следует ясно представлять себе их преимущества и недостатки, чтобы использовать каждый из них там, где он дает наилучшие результаты, и сочетать их так, чтобы получить лучшее из обоих миров.
Динамический полиморфизм предстает перед нами в форме классов с виртуальными функциями и объектов, работа с которыми осуществляется косвенно — через указатели или ссылки. Статический полиморфизм включает шаблоны классов и функций.
Полиморфизм означает, что данное значение может иметь несколько типов, а данная функция может принимать аргументы типов, отличающихся от точных типов ее параметров. "Полиморфизм представляет собой способ получить немного свободы динамической проверки типов, не теряя преимуществ статической проверки" — [Webber03].
Сила полиморфизма состоит в том, что один и тот же фрагмент кода может работать с разными типами, даже с теми, которые не были известны в момент написания этого кода. Такая "применимость задним числом" является краеугольным камнем полиморфизма, поскольку существенно увеличивает пригодность и возможность повторного использования кода (см. рекомендацию 37). (В противоположность этому мономорфный код работает только со строго конкретными типами, теми, для работы с которыми он изначально создавался.)
Динамический полиморфизм позволяет значению иметь несколько типов посредством открытого наследования. Например, Derived* p
можно рассматривать как указатель не только на Derived
, но и на объект любого типа Base
, который прямо или косвенно является базовым для Derived
(свойство категоризации). Динамический полиморфизм известен также как включающий полиморфизм, поскольку множество, моделируемое Base
, включает специализации, моделируемые Derived
.
Благодаря своим характеристикам динамический полиморфизм в С++ наилучшим образом подходит для решения следующих задач.