Самые важные вещи о шаблонах C++… извлеченные уроки

Что вы знаете о шаблонах наиболее важно: скрытые функции, распространенные ошибки, лучшие и наиболее полезные практики, советы...распространенные ошибки / недосмотр / предположения

Я начинаю реализовывать большую часть своей библиотеки /API с использованием шаблонов и хотел бы собрать наиболее распространенные шаблоны, советы и т. Д., Найденные на практике.

Позвольте мне формализовать вопрос: что самое важное вы узнали о шаблонах?

Пожалуйста, попробуйте привести примеры - это будет легче понять, в отличие от запутанных и чрезмерно сухих описаний.

Спасибо

12 ответов

Решение

Из "Исключительного стиля C++", пункт 7: разрешение перегрузки функции происходит до специализации шаблонов. Не смешивайте перегруженные функции и специализации шаблонных функций, иначе вас ждет неприятный сюрприз, когда функция действительно вызывается.

template<class T> void f(T t) { ... }   // (a)
template<class T> void f(T *t) { ... }  // (b)
template<> void f<int*>(int *t) { ... } // (c)
...
int *pi; f(pi); // (b) is called, not (c)!

В верхней части пункта 7:

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

Случай 1:

template<class T> void f(T t) { ... }  // (a)
template<class T> void f(T *t) { ... } // (b)
template<> void f(int *t) { ... }      // (c) - specializes (b)
...
int *pi; f(pi); // (c) is called

Случай 2:

template<class T> void f(T t) { ... }  // (a)
template<> void f(int *t) { ... }      // (c) - specializes (a)
template<class T> void f(T *t) { ... } // (b)
...
int *pi; f(pi); // (b) is called

Это не может быть популярным, но я думаю, что это должно быть сказано.

Шаблоны сложные.

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

И самое главное, держитесь подальше от шаблонного метапрограммирования...

Я бы сказал, что шаблон любопытных повторяющихся шаблонов (CRTP) Коплиена - это один шаблонный трюк, к которому я снова и снова обращаюсь. По сути, это позволяет вам вводить статически настраиваемую функциональность в производный класс, наследуя его от базового класса, параметризованного в имени производного класса. Потрясающе, но удивительно полезно (некоторые называют это статическим полиморфизмом).

Кроме того, я прислушаюсь к совету Нила Баттерворта о том, чтобы прочитать "Шаблоны C++" и добавить " Современный дизайн C++" Александреску.

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

#include <iostream>
#include <vector>

namespace std {
    template<>
    void vector<int>::clear() {
    std::cout << "Clearing..." << std::endl;
    resize(0);
    }
}

int main() {
    std::vector<int> v;
    v.push_back(1);
    v.clear();
}

выходы: очистка...

Этот вопрос немного напоминает: "Я собираюсь реализовать большую часть своей библиотеки с использованием функций, каковы распространенные ошибки при использовании функций?" Трудно придумать разумные ответы на такие вопросы, но вот мой совет - прочитайте хорошую книгу. Я рекомендую " Шаблоны C++" от Vandevoorde & Josuttis,

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

template <typename T>
class A {
public:
  template <typename S>
  A(A<S> const &);

  template <typename S>
  A & operator=(A<S> const &);

private:
  int * i;
}; 

Хотя эти функции выглядят как конструктор копирования и оператор присваивания копии, компилятор не видит его таким образом и все равно генерирует неявные версии. В результате любые действия, выполняемые этими функциями (например, глубокое копирование члена), не будут выполняться, когда объект копируется или назначается из того же типа:

void foo (A<int>);

void bar () {
  A<int> a1;
  foo (a1);   // Implicitly generated copy ctor called

  A<long> a2;
  foo (a2);   // Template ctor called.

  A<int> a3;
  a3 = a1;   // Implicitly generated copy assignment operator called

  a3 = a2;   // Template assignment operator called
}

Причиной такого поведения является специальное правило разрешения перегрузки (13.3.3):

С учетом этих определений жизнеспособная функция F1 определяется как лучшая функция, чем другая жизнеспособная функция F2, если для всех аргументов i ICSi(F1) не хуже последовательности преобразования, чем ICSi(F2), а затем

[...]

- F1 - это не шаблонная функция, а F2 - специализация шаблона функции, или, если нет,

В приведенных выше примерах разрешение перегрузки видит две функции с одинаковой сигнатурой, одна из которых является шаблоном. Не шаблонная функция (неявно сгенерированный конструктор копирования / оператор копирования) побеждает и так называется.

Прочитайте книги Майерса "Эффективные STL и C++" и "Современный дизайн C++" Александреску.

Мейерс расскажет вам, как легко совершать ошибки и как их избежать. Александреску знакомит вас с моделью программирования на основе шаблонов, в которой вы должны спросить: "Это действительно хорошая идея?" вся книга.

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

В целом, это помогает думать при наборе текста о том, что собирается делать компилятор, и как будет генерироваться код для каждого типа.

Будучи очень итеративным в разработке и построении шаблонной сложности понемногу помогло мне избежать погружения в сообщения об ошибках компиляции. Не забывайте хранить где-нибудь простую (или фальшивую) копию шаблона, иначе у вас могут возникнуть неприятные сюрпризы, когда вы впервые создадите шаблон монстра.

Наконец, когда нет выхода, прочитайте эти сообщения об ошибках компиляции! Поначалу они могут выглядеть довольно страшно, но они действительно полезны. Возможно, сначала извлекая первый, копируя его в текстовом редакторе и придавая ему красивый вид, вы привыкнете к ним, и он быстро станет второй натурой для прочтения.

STL твой друг.

Вот несколько правил:

  1. Не пишите никаких шаблонов, если вы не пишете очень, очень общую библиотеку (STL и Boost - два ярких примера).
  2. Не создавайте экземпляры какого-либо нетривиального шаблона слишком много раз. Создание огромных шаблонных классов особенно излишне. Вы должны рассмотреть возможность использования наследования и полиморфизма (я имею в виду простой способ использования виртуальных функций).
  3. Если вы пишете какие-либо шаблоны, зная, когда использовать const, mutable а также volatile сэкономит пользователям шаблона время компиляции и выполнения.
  4. Если вы создаете какие-либо шаблоны, используйте хороший компилятор.

Важно понимать отдельную компиляцию, и вероятность увеличения размера исполняемого файла увеличивается. Если вы создадите экземпляр шаблона с одним и тем же типом в нескольких файлах C++, вы получите тип, воспроизводимый несколько раз, по крайней мере, на некоторых компиляторах.

Я часто использовал C++ и шаблоны, в том числе более сложное метапрограммирование шаблонов, и мне кажется, что их полезность переоценена. Первоначально они были добавлены в язык с ++, задолго до создания с ++, чтобы использовать общие возможности программирования. Это просто позволяет сосредоточиться на логике кода без учета типов, потенциально делая код более понятным и пригодным для повторного использования.

Моя философия программирования состоит в том, чтобы понять оригинальную цель и дизайн языка и его особенности, чтобы действительно ценить язык. Я чувствую, что метапрограммирование шаблонов - это ублюдение шаблонов, и его следует избегать. Тем не менее, шаблоны полезны для определения универсальных типов более высокого уровня, таких как случай кортежей.

Другие вопросы по тегам