Как правильно скопировать массив и при чем тут SFINAEИсточник: habrahabr kibergus
Копировать элементы из одного контейнера в другой? Нет ничего проще, универсальный алгоритм помещается в 5 строк: Возможно вы узнали реализацию std::copy с cplusplus.com. Почему же реализация std::copy из GNU STL занимает 139 строк? Давайте разберемся.STL - это самая базовая библиотека, используемая всеми нормальными программами на C++. Поэтому она должна предоставлять максимально эффективные реализации алгоритмов. Причем эффективные во многих смыслах:
Скорость выполненияКод можно сделать более быстрым, если учитывать особенности типов, которыми инстанциируется шаблон. Например, если тип имеет тривиальный конструктор копирования, его можно копировать побайтно. И если объекты лежат в непрерывной области памяти, вместо многократного вызова конструктора можно использовать старую добрую C функцию memmove, которая может задействовать векторные команды процессора, копирующие данные особенно быстро. Обратите внимание, что реализация std::copy не может использовать memcpy, так как memcpy работает только на не перекрывающихся областях памяти.Таким образом, мы хотим написать две реализации std::copy: одну быструю, для тривиально копируемых типов, а другую универсальную, для всех остальных. SFINAEИ тут на помощь приходит приём, известный как "substitution failure is not an error". Если при подстановке шаблонных параметров получается некорректное выражение, это не является ошибкой. Компилятор должен проигнорировать шаблон и продолжить поиск. Важно, что в случае шаблонной функции некорректное выражение должно обнаружиться не в теле функции, когда конкретный шаблон уже выбран и продолжать поиск некуда, а в её прототипе. Самый простой способ это сделать - использовать std::enable_if.std::enable_ifСам по себе enable_if очень прост. Это шаблон от интегральной константы и типа. Если константа истинна, то он содержит объявление вложенного типа с именем type, в противном случае он пуст. Тип будет определен и иметь тип Type, но только если условие истинно. Используют его вот так:
type traitsЧтобы все описанное выше имело смысл, нужно иметь константы, зависящие от типа. Они называются type traits. Это шаблонные структуры, у которых есть статическое константное публичное свойство value, принимающее значение true или false, в зависимости от типа, которым инстанциирован шаблон. Некоторые из них описаны с помощью частичной специализации шаблона, некоторые реализуются самим компилятором, а некоторые построены как выражения над другими type traits.Специальная реализация std::copyДобавим перед универсальной реализацией вот такую: Если copy будет вызван с тремя указателями на тривиально копируемый тип, то компилятор применит эту оптимизированную реализацию. В противном случае, этот шаблон будет проигнорирован и будет использована универсальная версия.В реальной библиотеке все немного сложнее, потому что стандартом зафиксировано, что std::copy имеет два шаблонных параметра. Если программист явно их укажет, то все равно надо выбрать оптимизированный вариант. Поэтому различные реализации описаны под служебными именами, а в самом std::move находится код, выбирающий наиболее подходящую реализацию. Вот значительно упрощенный вариант: Кроме того, в GNU STL применяется еще одна оптимизация: для итераторов случайного доступа применяется цикл for с вычислением количества итераций. Компилятор умеет разворачивать такие циклы, увеличивая быстродействие программы.
Размер генерируемого кодаОбратите внимание, что шаблон, приведенный в начале статьи, для каждого нового типа будет генерировать полностью новый код. Второй же шаблон вырождается в одно вычитание и одно сложение указателей, а реализация memmove общая для всех типов. То есть помимо ускорения программы, уменьшается её размер. Аналогичные трюки могут применяться в контейнерах: части, не зависящие от хранимого типа выносятся из шаблона и используют общую реализацию. Например, в реализации std::list шаблонная структура, хранящая данные, не содержит ничего кроме самих данных. Ссылки на соседей и операции над ними реализованы в базовом не шаблонном классе, от которого она наследуется.Используйте библиотечные функцииМораль этой статьи проста: используйте алгоритмы, предоставляемые стандартной библиотекой как можно чаще. Это делает ваши программы эффективнее, понятнее и гибче.Эффективнее потому, что разработчики стандартных библиотек могут применять оптимизации, которые вы не можете позволить себе в обычном коде. Вы же не готовы писать и поддерживать несколько реализаций копирования объектов под десяток платформ? У прикладного программиста на это нет времени. Понятнее потому, что вы можете писать короткий высокоуровневый код, оперируя примитивами, знакомыми вашим коллегам. Гибче потому, что вам не надо делать преждевременных оптимизаций и потому, что вы используете более общие алгоритмы, чем вы бы написали сами. P.S.Обратите внимание, что std::copy знает, что result не находится между start и finish, но использует функцию memmove, которая вынуждена это проверить и выбрать направление копирования (от начала к концу или от конца к началу). Можно было сделать чуть более оптимальную реализацию, но тогда бы разработчики STL должны были бы поддерживать специальные релизации под каждую из поддерживаемых архитектур. На это уже тратят свое время разработчики glibc. Дублировать эту работу никому не надо. |