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

Я думаю критический раздел от , FAQ C++, Облегченный :

А законное, долгосрочное использование для частного наследования состоит в том, когда Вы хотите создать класс Fred, который использует код в классе Wilma и код от класса, Wilma должна вызвать функции членства от Вашего нового класса, Fred. В этом случае Fred называет non-virtuals в Wilma и вызовы Wilma (обычно чистый virtuals) сам по себе, которые переопределяются Fred. Это намного тяжелее относилось бы к составу.

, Если в сомнении, необходимо предпочесть состав по частному наследованию.

54
задан Roddy 7 October 2008 в 09:43
поделиться

12 ответов

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

Подумайте об этом ...

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
ответ дан 7 November 2019 в 07:35
поделиться

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

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

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

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

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

2
ответ дан 7 November 2019 в 07:35
поделиться

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

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

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

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

2
ответ дан 7 November 2019 в 07:35
поделиться

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

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

2
ответ дан 7 November 2019 в 07:35
поделиться

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

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

, вызванный как

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

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

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

3
ответ дан 7 November 2019 в 07:35
поделиться

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

2
ответ дан 7 November 2019 в 07:35
поделиться

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


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

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

4
ответ дан 7 November 2019 в 07:35
поделиться

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

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
10
ответ дан 7 November 2019 в 07:35
поделиться
  • шаблоны являются типизированными.
  • шаблонные объекты / типы могут иметь пространство имен, быть закрытыми членами класса и т. Д.
  • параметры шаблонных функций не реплицируются по всему телу функции.

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

5
ответ дан 7 November 2019 в 07:35
поделиться

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

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

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

4
ответ дан 7 November 2019 в 07:35
поделиться

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

12
ответ дан 7 November 2019 в 07:35
поделиться

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

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

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

Ссылки:

22
ответ дан 7 November 2019 в 07:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: