Являются ли шаблоны C ++ просто замаскированными макросами? - PullRequest
53 голосов
/ 08 октября 2008

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

Теперь я пытаюсь глубже интегрировать шаблоны в мой ОО-проект, и мне все время возвращается мучительная мысль: на самом деле это всего лишь макросы ... Вы можете реализовать (скорее УГЛЫЙ) auto_ptrs, используя #defines, если ты действительно хотел.

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

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

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

Ответы [ 25 ]

4 голосов
/ 14 декабря 2009

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

Для некоторых реальных примеров прочитайте Современное программирование на С ++ , написанное Андре Александреску, или Метапрограммирование C ++ , написанное Дэйвом Абрахамсом и Алексеем Гуртовым Практически ничто из того, что сделано в обеих книгах, не может быть смоделировано с помощью препроцессора, кроме как в крайне минимальной степени.

Редактировать: Что касается 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 сообщает компилятору, что определенное имя предназначено для ссылки на тип, а не на переменную / значение, поэтому (например) вы можете определить другие переменные этого типа.

4 голосов
/ 08 октября 2008

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

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;
}

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

2 голосов
/ 06 марта 2009

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

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

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

2 голосов
/ 08 октября 2008

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

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

... шаблоны могут указывать на то, что некоторые параметры имеют одинаковый тип и т. Д. *


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

2 голосов
/ 08 октября 2008

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

2 голосов
/ 08 октября 2008

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

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

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

2 голосов
/ 08 сентября 2009

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

Учтите это ...

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 по шаблонам ! Вы можете использовать шаблоны, чтобы делать то, что называется чертами, у которых есть своего рода документация для типов и тому подобное. С шаблонами вы можете сделать гораздо больше, чем с макросами!

2 голосов
/ 14 декабря 2009

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

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

2 голосов
/ 14 декабря 2009

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

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

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

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

2 голосов
/ 14 декабря 2009

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...