Введение В Магию Шаблонов

Шаблоны в C++ являются инструментами метапрограммирования и реализуют полиморфизм во время компиляции.

Что это? Это когда мы пишем код с полиморфным поведением, но само поведение определяется на этапе компиляции — то есть, в отличие от полиморфизма виртуальных функций, результирующий двоичный код уже будет иметь постоянное поведение.



За что?



Введение в магию шаблонов

Используем шаблоны для красоты.

Каждый разработчик C++ знает, что такое красота; красота - это когда код компактный , понятно и быстро .



Мета-магия и неявные интерфейсы

Что такое метапрограмма? Метапрограмма — это программа, результатом работы которой станет другая программа.

В C++ компилятор выполняет метапрограммы, и результатом является двоичный файл.



Введение в магию шаблонов

Шаблоны используются для написания метапрограмм.

Чем еще полиморфизм шаблонов отличается от полиморфизма виртуальных функций? Если класс имеет явный интерфейс, который мы определили в объявлении класса, то в дальнейшем в программе объекты этого типа могут использоваться в соответствии с этим самым интерфейсом.

Но для шаблонов мы используем неявные интерфейсы, т.е.

используя объект типа, который мы определяем интерфейс неявного типа , который компилятор выведет при построении метапрограммы.



Первые заклинания: волшебная дубина



Введение в магию шаблонов

Давайте усовершенствуем наш шаблон и посмотрим, какие типы мы получили для различных параметров шаблона:
  
  
  
  
  
  
  
  
  
  
  
  
   

typedef char CHAR; int main() { B<int> b; B<char> c; B<unsigned char> uc; B<signed char> sc; B<CHAR> C; B<char, 1> c1; B<char, 2-1> c21; cout << "b=" << typeid(b).

name() << endl; cout << "c=" << typeid(c).

name() << endl; cout << "C=" << typeid(C).

name() << endl; cout << "sc=" << typeid(sc).

name() << endl; cout << "uc=" << typeid(uc).

name() << endl; cout << "c1=" << typeid(c1).

name() << endl; cout << "c21=" << typeid(c21).

name() << endl; return 0; }

Вывод программы показывает, что типы экземпляров шаблонов разные даже для эквивалентных типов — unsigned char & char. Более того, они идентичны для char и CHAR, поскольку typedef не создает тип, а просто присваивает ему другое имя.

Они идентичны для выражений 1 и 2-1, поскольку компилятор оценивает выражения и использует 1 вместо 2-1. Это значит, что мы не можем без дополнительных проблем использовать отдельную компиляцию для шаблонов: ааа

#include <iostream> using namespace std; template <typename T> class A { public: void f(); };

main.cpp

#include "export.h" int main() { A<int> a; a.f(); return 0; }

.

cpp

#include "a.h" template <typename T> void A<T>::f() { cout << "A<t>::f" << endl; } template class A<int>;

В общем, в стандарте C++ для этого есть кое-что: ключевое слово экспорта , однако эта возможность слишком сложна в реализации и отсутствует в большинстве компиляторов.

Существуют компиляторы, которые его поддерживают, но я не рекомендую использовать его в переносимом коде.

Помимо классов существуют еще шаблоны функций:

template<typename T> T func(T t, T d) { cout << "func" << endl; }; int main() { func('1', 2); }

Если компилятор может определить тип параметра шаблона по типу параметров, он так и сделает, и нам не нужно указывать его в коде.

Если нет, то мы можем определить разрешающую функцию:

inline int func(char c, int i) { return func<int>(c, i); };

Это не влечет за собой никаких накладных расходов.



Специализация – новый уровень



Введение в магию шаблонов

Обычно с помощью шаблонов мы хотим написать универсальный код, но в некоторых случаях мы можем потерять в производительности.

Для решения проблемы существует специальное заклинание – шаблонная специализация.

Специализация — это переопределение шаблона с определенным типом или тип класса :

#include <iostream> using namespace std; template<typename T> T func(T t) { cout << "func" << endl; }; template<typename T> T * func(T *t) { cout << "func with pointer!" << endl; }; int main() { func(2); int i = 2; func(&i); }

Компилятор сам выберет наиболее точно подходящую специализацию, в примере это класс типа «указатель на тип».



Зловещая магия: Рекурсия

Специализация и тот факт, что мы можем использовать шаблоны внутри шаблонов, дают нам одну очень интересную особенность — рекурсию времени компиляции.



Введение в магию шаблонов

Самый простой и популярный пример — вычисление какого-нибудь ряда или многочлена, скажем, суммы ряда натуральных чисел:

#include <iostream> using namespace std; template <int i> int func() { return func<i-1>()+i; }; template <> int func<0>() { return 0; }; int main () { cout << func<12>() << endl; return 0; };

Посмотрим.

Это работает! Прохладный? Увеличим количество итераций до 500:

cout << func<500>() << endl;

Теперь компиляция занимает больше времени, но время выполнения программы остается постоянным! Чудеса!

Не делай козла, если хочешь грозу

Здесь есть пара моментов.



Введение в магию шаблонов

Максимальная глубина рекурсии по умолчанию ограничена реализацией, для нового gcc она равна 900, для старых версий меньше.

Параметр

$ g++ recursion.cpp -ftemplate-depth=666666666

снимает это ограничение.

Вторая ловушка — не ждите отчетов об ошибках.

Измените сумму на факториал:

int func() { return func<i-1>() * i; }; template <> int func<0>() { return 1; }; .

cout << func<500>() << endl;

Получаем неверный результат и ни одного предупреждения.

Третий момент очевиден: мы можем создать слишком много почти одинаковых экземпляров шаблона и Вместо прироста производительности получите прирост бинарного кода .



Мощные заклинания древних

Можно ли совместить магию наследования с магией шаблонов?

Введение в магию шаблонов

Древние для этого использовали заклинание ЦРТП .

Идея проста: использовать невиртуальное наследование и обеспечивать полиморфное поведение, явно приводя тип наследника к типу родителя.

Давайте рассмотрим пример использования :

template<typename Filtrator> class FiltratorImpl { inline void find_message(.

) { Filtrator* filtrator = static_cast<Filtrator* >(this); … filtrator->find_and_read_message(info, collection); } }; .

class CIFSFiltrator : public FiltratorImpl<CIFSFiltrator> { .

inline void find_and_read_message(PacketInfo& info) {.

} .

}; class RPCFiltrator : public FiltratorImpl<RPCFiltrator> { .

inline void find_and_read_message(PacketInfo& info) {.

} .

};

Мы получаем унаследованные встроенные методы с полиморфным поведением! Кто говорит, что это не круто, тот мой враг навсегда.

Еще древние советуют добавлять в родительский конструктор что-то вроде этого:

static_assert(std::is_member_function_pointer<decltype(&Filtrator::find_and_read_message)>::value)

Чтобы демоны, разбуженные мощным заклинанием, не смогли навредить магу, их вызвавшему.



Введение в магию шаблонов

Есть еще много секретных техник, древних и не очень.

Надеюсь скоро увидеть тебя /*в аду*/, и да пребудет с тобой сила древних.

Теги: #шаблон #производительность #C++

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.