Являются ли шаблоны С++ просто маскируемыми?

Я программировал на С++ в течение нескольких лет, и я использовал STL совсем немного и несколько раз создал свои собственные классы шаблонов, чтобы увидеть, как это делается.

Теперь я пытаюсь интегрировать шаблоны глубже в мой OO-дизайн, и острая мысль продолжает возвращаться ко мне: они просто макросы, действительно... Вы могли бы реализовать (скорее UGLY) auto_ptrs, используя #defines, если вы действительно хотели.

Этот способ мышления о шаблонах помогает мне понять, как мой код действительно будет работать, но я чувствую, что мне как-то не хватает смысла. Макросы означают воплощение зла, но "шаблонное метапрограммирование" - это все ярость.

Итак, каковы реальные различия? и как шаблоны могут избежать опасностей, с которыми ведет #define, например

  • Неисправные ошибки компилятора в места, где вы их не ожидаете?
  • Накидка кода?
  • Сложность в отслеживании кода?
  • Настройка точек останова отладчика?

Ответ 1

Макросы - это механизм замены текста.

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

Ответ 2

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

Да, это одно и то же: инструменты генерации кода.

Макросы - это примитивная форма, без особого применения компилятором (например, выполнение объектов в C - это можно сделать, но это не очень красиво). Шаблоны более продвинуты и имеют намного лучшую проверку типов компиляторов, сообщения об ошибках и т.д.

Однако у каждого есть сильные стороны, которых нет у другого.

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

С другой стороны, шаблоны могут выполнять действительно СМЕШНЫЕ вещи, которые невозможны с помощью макросов. Например:

template<int d,int t> class Unit
{
    double value;
public:
    Unit(double n)
    {
        value = n;
    }
    Unit<d,t> operator+(Unit<d,t> n)
    {
        return Unit<d,t>(value + n.value);
    }
    Unit<d,t> operator-(Unit<d,t> n)
    {
        return Unit<d,t>(value - n.value);
    }
    Unit<d,t> operator*(double n)
    {
        return Unit<d,t>(value * n);
    }
    Unit<d,t> operator/(double n)
    {
        return Unit<d,t>(value / n);
    }
    Unit<d+d2,t+t2> operator*(Unit<d2,t2> n)
    {
        return Unit<d+d2,t+t2>(value * n.value);
    }
    Unit<d-d2,t-t2> operator/(Unit<d2,t2> n)
    {
        return Unit<d-d2,t-t2>(value / n.value);
    }
    etc....
};

#define Distance Unit<1,0>
#define Time     Unit<0,1>
#define Second   Time(1.0)
#define Meter    Distance(1.0)

void foo()
{
   Distance moved1 = 5 * Meter;
   Distance moved2 = 10 * Meter;
   Time time1 = 10 * Second;
   Time time2 = 20 * Second;
   if ((moved1 / time1) == (moved2 / time2))
       printf("Same speed!");
}

Шаблон позволяет компилятору динамически создавать и использовать безопасные для типов экземпляры шаблона на лету. Во время компиляции компилятор фактически выполняет математику параметра-шаблона, создавая отдельные классы, где это необходимо для каждого уникального результата. Существует неявный тип Unit <1, -1> (расстояние/время = скорость), который создается и сравнивается в условном выражении, но никогда явно не объявляется в коде.

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

Ответ 3

Они анализируются компилятором, а не препроцессором, который выполняется перед компилятором.

Здесь MSDN говорит об этом: http://msdn.microsoft.com/en-us/library/aa903548(VS.71).aspx

Вот некоторые проблемы с макросом:

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

Если этого недостаточно для вас, я не знаю, что есть.

Ответ 4

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

  • Например, макросы не обеспечивают безопасность типа, а функциональные шаблоны: для компилятора нет возможности проверить, что макропараметры совместимы, - также в момент создания шаблона функции компилятор знает, t20 > или float определить operator + Шаблоны
  • открывают дверь для метапрограммирования (короче говоря, оценивая вещи и принимая решение во время компиляции): во время компиляции можно узнать, является ли тип интегральным или плавающим; независимо от того, является ли он указателем или он является константным, и т.д. см. "черты типа" в предстоящем С++ 0x
  • Шаблоны классов имеют частичную специализацию
  • функциональные шаблоны имеют полную полную специализацию, в вашем примере add<float>(5, 3); может быть реализовано иначе, чем add<int>(5, 3);, что невозможно с макросами
  • макрос не имеет области
  • #define min(i, j) (((i) < (j)) ? (i) : (j)) - параметры i и j оцениваются дважды. Например, если какой-либо параметр имеет постинкрементную переменную, инкремент выполняется два раза
  • поскольку макросы расширяются препроцессором, сообщения об ошибках компилятора будут ссылаться на расширенный макрос, а не на определение макроса. Кроме того, макрос будет отображаться в расширенной форме во время отладки
  • и т.д...

Примечание. В некоторых редких случаях я предпочитал полагаться на переменные макросы, потому что нет такой вещи, как вариативные шаблоны, до тех пор, пока С++ 0x не станет основным. C++11 в прямом эфире.

Литература:

Ответ 5

На самом базовом уровне да, шаблон - это просто замена макросов. Но вы многое пересказываете, думая об этом таким образом.

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

template <typename T>
struct is_void
{
    static const bool value = false;
}

template <>
struct is_void<void>
{
    static const bool value = true;
}

Это само по себе является лишь одним из примеров многих вещей, которые вы можете сделать. Шаблоны сами по себе являются Тьюрингом.

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

Ответ 6

НЕТ. Один простой пример счетчика: шаблоны находятся в пространствах имен, макросы игнорируют пространства имен (поскольку они являются инструкциями препроцессора).

namespace foo {
    template <class NumberType>
    NumberType add(NumberType a, NumberType b)
    {
        return a+b;
    }

    #define ADD(x, y) ((x)+(y))
} // namespace foo

namespace logspace 
{
    // no problemo
    template <class NumberType>
    NumberType add(NumberType a, NumberType b)
    {
        return log(a)+log(b);
    }

    // redefintion: warning/error/bugs!
    #define ADD(x, y) (log(x)+log(y))

} // namespace logspace

Ответ 7

С++ шаблоны вроде как Lisp макросы (а не макросы), поскольку они работают с уже обработанной версией кода и позволяют генерировать произвольный код во время компиляции. К сожалению, вы программируете что-то похожее на исходное исчисление Lambda, поэтому передовые методы, такие как цикл, являются громоздкими. Для всех подробностей о деталях см. "Генеративное программирование" Криштофа Чарнецкого и Ульриха Эйзенекера.

Ответ 8

В случае, если вы ищете более углубленное изучение предмета, я могу превратить вас в всех любимый С++ hater. Этот человек знает и ненавидит больше С++, чем я могу когда-либо мечтать. Это одновременно делает FQA невероятно воспалительным и отличным ресурсом.

Ответ 9

  • шаблоны являются типичными.
  • шаблонные объекты/типы могут быть помещены в пространство имен, сделаны частными членами класса и т.д. Параметры
  • для шаблонных функций не реплицируются по всему телу функции.

Это действительно большое дело и предотвращает множество ошибок.

Ответ 10

Что-то, о чем не упоминалось, это то, что функции шаблонов могут выводить типы параметров.

template <typename T>
void func(T t)
{
  T make_another = t;

Можно утверждать, что предстоящий оператор "typeof" может исправить это, но даже он не может разбить другие шаблоны:

template <typename T>
void func(container<T> c)

или даже:

template <tempate <typename> class Container, typename T>
void func(Container<T> ct)

Я также чувствую, что предмет специализации недостаточно освещен. Вот простой пример того, что макросы не могут сделать:

template <typename T>
T min(T a, T B)
{
  return a < b ? a : b;
}

template <>
char* min(char* a, char* b)
{
  if (strcmp(a, b) < 0)
    return a;
  else
    return b;
}

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

Ответ 11

Этот ответ предназначен для прояснения препроцессора C и того, как он может использоваться для общего программирования


Они в некоторых отношениях, поскольку они позволяют использовать некоторую подобную семантику. Препроцессор C использовался для создания общих структур данных и алгоритмов (см. токена Concaination). Однако без учета каких-либо других особенностей шаблонов на С++ он делает всю родовую игру программирования LOT CLEARER для чтения и реализации.

Если кто-то хочет видеть хардкор C, то только общее программирование в действии читает libevent sourcecode - это также упоминается . Реализована обширная коллекция контейнеров/алгоритмов, и она выполнена в заголовочном файле SINGLE (очень читаема). Я действительно восхищаюсь этим, код шаблона С++ (который я предпочитаю для других его атрибутов) ОЧЕНЬ многословный.

Ответ 12

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

Для некоторых реальных примеров прочитайте Modern С++ Programming, Andre Alexandrescu или С++ Metaprogramming Дейвом Абрахамом и Алексеем Гуртовым. Почти ничего, что делается в любой книге, не может быть смоделировано до предельно минимальной степени с препроцессором.

Изменить. Что касается typename, это требование довольно просто. Компилятор не всегда может определить, относится ли зависимое имя к типу или нет. Использование typename явно сообщает компилятору, что оно относится к типу.

struct X { 
    int x;
};

struct Y {
    typedef long x;
};

template <class T>
class Z { 
    T::x;
};

Z<X>; // T::x == the int variable named x
Z<Y>; // T::x == a typedef for the type 'long'

typename сообщает компилятору, что конкретное имя предназначено для обращения к типу, а не переменной/значению, поэтому (например) вы можете определить другие переменные этого типа.

Ответ 13

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

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

С макросами всегда есть шанс, что некоторое выражение будет оцениваться дважды. Представьте, что вы передаете что-то вроде ++ x в качестве параметра.

Ответ 14

Шаблоны могут быть помещены в пространства имен или быть членами класса. Макросы - это только шаг предварительной обработки. В принципе, шаблоны являются членами первого класса языка, который играет красиво (лучше?) Со всем остальным.

Ответ 15

Шаблоны могут делать намного больше, чем макропроцессор.

Например. Существуют шаблонные специализации: если этот шаблон создается с этим типом или константой, то не используйте реализацию по умолчанию, но эту здесь...

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


Вот несколько источников, на которые вы можете посмотреть:

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

Ответ 16

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

template<class T>
void Garbage(int a, int b)
{
    fdsa uiofew & (a9 s) fdsahj += *! wtf;
}

Изменить: Этот пример относится только к Visual C++. В стандарте C++ ваш код шаблона фактически анализируется в синтаксическом дереве до того, как шаблон когда-либо используется, поэтому пример принимается V C++, но не GCC или Clang. (Я узнал об этом, когда попытался перенести код V C++ в GCC, и мне пришлось иметь дело с сотнями синтаксических ошибок в моих неспециализированных шаблонах.) Однако синтаксическое дерево по-прежнему не обязательно имеет смысл в семантическом смысле. Независимо от компилятора, проверка типа в теле не происходит, пока вы не создадите экземпляр шаблона, предоставив <template arguments>.

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

Ответ 17

По-моему, макросы - это плохая привычка от C. Хотя они могут быть полезны для некоторых, я не вижу реальной потребности в них, когда есть typedefs и templates. Шаблоны являются естественным продолжением объектно-ориентированного программирования. Вы можете сделать намного больше с шаблонами...

Рассмотрим это...

int main()
{
    SimpleList<short> lstA;
    //...
    SimpleList<int> lstB = lstA; //would normally give an error after trying to compile
}

Чтобы сделать преобразование, вы можете использовать что-то, что называется конструктором преобразования и конструктором последовательности (посмотрите на конец) по довольно полному примеру для списка:

#include <algorithm>

template<class T>
class SimpleList
{
public:
    typedef T value_type;
    typedef std::size_t size_type;

private:
    struct Knot
    {
        value_type val_;
        Knot * next_;
        Knot(const value_type &val)
        :val_(val), next_(0)
        {}
    };
    Knot * head_;
    size_type nelems_;

public:
    //Default constructor
    SimpleList() throw()
    :head_(0), nelems_(0)
    {}
    bool empty() const throw()
    { return size() == 0; }
    size_type size() const throw()
    { return nelems_; }

private:
    Knot * last() throw() //could be done better
    {
        if(empty()) return 0;
        Knot *p = head_;
        while (p->next_)
            p = p->next_;
        return p;
    }

public:
    void push_back(const value_type & val)
    {
        Knot *p = last();
        if(!p)
            head_ = new Knot(val);
        else
            p->next_ = new Knot(val);
        ++nelems_;
    }
    void clear() throw()
    {
        while(head_)
        {
            Knot *p = head_->next_;
            delete head_;
            head_ = p;
        }
        nelems_ = 0;
    }
    //Destructor:
    ~SimpleList() throw()
    { clear(); }
    //Iterators:
    class iterator
    {
        Knot * cur_;
    public:
        iterator(Knot *p) throw()
        :cur_(p)
        {}
        bool operator==(const iterator & iter)const throw()
        { return cur_ == iter.cur_; }
        bool operator!=(const iterator & iter)const throw()
        { return !(*this == iter); }
        iterator & operator++()
        {
            cur_ = cur_->next_;
            return *this;
        }
        iterator operator++(int)
        {
            iterator temp(*this);
            operator++();
            return temp;
        }
        value_type & operator*()throw()
        { return cur_->val_; }
        value_type operator*() const
        { return cur_->val_; }
        value_type operator->()
        { return cur_->val_; }
        const value_type operator->() const
        { return cur_->val_; }
    };
    iterator begin() throw()
    { return iterator(head_); }
    iterator begin() const throw()
    { return iterator(head_); }
    iterator end() throw()
    { return iterator(0); }
    iterator end() const throw()
    { return iterator(0); }
    //Copy constructor:
    SimpleList(const SimpleList & lst)
    :head_(0), nelems_(0)
    {
        for(iterator i = lst.begin(); i != lst.end(); ++i)
            push_back(*i);
    }
    void swap(SimpleList & lst) throw()
    {
        std::swap(head_, lst.head_);
        std::swap(nelems_, lst.nelems_);
    }
    SimpleList & operator=(const SimpleList & lst)
    {
        SimpleList(lst).swap(*this);
        return *this;
    }
    //Conversion constructor
    template<class U>
    SimpleList(const SimpleList<U> &lst)
    :head_(0), nelems_(0)
    {
        for(typename SimpleList<U>::iterator iter = lst.begin(); iter != lst.end(); ++iter)
            push_back(*iter);
    }
    template<class U>
    SimpleList & operator=(const SimpleList<U> &lst)
    {
        SimpleList(lst).swap(*this);
        return *this;
    }
    //Sequence constructor:
    template<class Iter>
    SimpleList(Iter first, Iter last)
    :head_(0), nelems_(0)
    {
        for(;first!=last; ++first)
            push_back(*first);


    }
};

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

Ответ 18

Представлено ключевое слово typename для включения контекстно-свободных вложенных typdef. Они были необходимы для метода признаков, которые позволяют добавлять метаданные к типам (особенно встроенным типам, таким как указатель), это было необходимо для записи STL. Ключевое слово typename в остальном совпадает с ключевым словом класса.

Ответ 19

Попробуйте примитивный пример. Рассмотрим

#define min(a,b) ((a)<(b))?(a):(b)

вызывается как

c = min(a++,++b);

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

Изменить. И нет, вы не можете обеспечить безопасность типов с помощью макросов. Как бы вы реализовали typeafe min() для каждого типа, определяющего меньше сравнения (т.е. operrator<)?

Ответ 20

Шаблоны понимают типы данных. Макросы не работают.

Это означает, что вы можете делать что-то вроде следующего...

  • Определите операцию (например, одну для номеров обертывания), которая может принимать любой тип данных, а затем предоставить специализации, которые выбирают соответствующий алгоритм на основе того, является ли тип данных интегральная или плавающая точка
  • Определите аспекты ваших типов данных во время компиляции, допустив трюки, такие как шаблонный вывод размера массива, который Microsoft использует для перегрузок С++ strcpy_s и его значок

Кроме того, поскольку шаблоны безопасны по типу, существует множество методов кодирования шаблонов, которые могли бы быть выполнены с некоторым гипотетическим продвинутым препроцессором, но в лучшем случае были бы клочковыми и подверженными ошибкам (например, параметры шаблона шаблона, аргументы шаблона по умолчанию, шаблоны политик, как обсуждалось в Modern С++ Design).

Ответ 21

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

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

Ответ 22

Это не ответ, как следствие уже высказанных ответов.

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

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

Ответ 23

Есть некоторые основные проблемы с макросами.

Во-первых, они не учитывают область действия или тип. Если у меня есть #define max(a, b)..., то всякий раз, когда у меня есть токен max в моей программе, по какой-то причине он будет заменен. Он будет заменен, если это имя переменной или глубоко внутри вложенных областей. Это может вызвать затруднительные ошибки компиляции. Напротив, шаблоны работают внутри системы типа С++. Функция шаблона может использовать свое имя во внутренней области и не будет пытаться переписать имя переменной.

Во-вторых, макросы не могут быть изменены. Шаблон std::swap обычно просто объявляет временную переменную и выполняет очевидные назначения, потому что это очевидный способ, который обычно работает. Это то, что будет ограничено макросом. Это было бы крайне неэффективно для больших векторов, поэтому векторы имеют специальный swap, который меняет местами ссылки, а не весь контент. (Это оказывается очень важным в материалах, которые средний программист на С++ не должен писать, но использует.)

В-третьих, макросы не могут выполнять какие-либо формы вывода типов. Во-первых, вы не можете написать общий своп-макрос, поскольку он должен будет объявить переменную типа, и он не знает, какой тип может быть. Шаблоны относятся к типу.

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

Ответ 24

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

Ответ 25

Шаблоны интегрированы в язык и безопасны по типу.

Расскажите, как вы это сделаете с помощью макросов. Это тяжелое метапрограммирование шаблонов.

https://www.youtube.com/watch?v=0A9pYr8wevk

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