64

私は数年間C++でプログラミングを行っており、STLをかなり使用しており、独自のテンプレートクラスを数回作成して、それがどのように行われるかを確認しています。

今、私はテンプレートをオブジェクト指向デザインにさらに深く統合しようとしていますが、しつこい考えが私に戻ってきます:それらは単なるマクロです、本当に...したかった。

このようなテンプレートの考え方は、コードが実際にどのように機能するかを理解するのに役立ちますが、どういうわけか要点を見逃しているに違いないと思います。マクロは邪悪な化身を意味しますが、「テンプレートメタプログラミング」は大流行しています。

それで、本当の違いは何ですか?そして、テンプレートは、#defineがあなたを導く危険をどのように回避することができますか?

  • 予期しない場所での不可解なコンパイラエラー?
  • コードの膨張?
  • コードのトレースが難しいですか?
  • デバッガブレークポイントを設定しますか?
4

25 に答える 25

51

マクロはテキスト置換メカニズムです。

テンプレートは、コンパイル時に実行され、C++型システムに統合された機能的なチューリング完全言語です。それらは、言語のプラグインメカニズムと考えることができます。

于 2008-10-07T20:46:51.070 に答える
38

これらは、コンパイラーの前に実行されるプリプロセッサーではなく、コンパイラーによって解析ます

これがMSDNがそれについて言っていることです:http: //msdn.microsoft.com/en-us/library/aa903548 (VS.71).aspx

マクロに関するいくつかの問題があります。

  • マクロパラメータが互換性のあるタイプであることをコンパイラが確認する方法はありません。
  • マクロは、特別な型チェックなしで展開されます。
  • iパラメータとjパラメータは2回評価されます。たとえば、いずれかのパラメーターにポストインクリメント変数がある場合、インクリメントは2回実行されます。
  • マクロはプリプロセッサによって展開されるため、コンパイラのエラーメッセージは、マクロ定義自体ではなく、展開されたマクロを参照します。また、マクロはデバッグ中に展開された形式で表示されます。

それがあなたにとって十分でないなら、私は何であるかわかりません。

于 2008-10-07T20:44:34.787 に答える
34

ここには、マクロとテンプレートを区別しようとするコメントがたくさんあります。

はい - どちらも同じものです: コード生成ツールです。

マクロはプリミティブな形式であり、コンパイラによる強制はあまりありません (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 以上のパラメータ (参照が必要) で定義しており、それぞれが異なる物理ユニット タイプを表しているようです。あなたの数字だけのために、その種のクラスの型安全性について考えてください。

于 2008-10-07T21:34:37.150 に答える
22

答えはとても長いので、すべてを要約することはできませんが、次のようになります。

  • たとえば、マクロは型の安全性を保証しませんが、関数テンプレートは保証します。
    コンパイラがマクロパラメータが互換性のある型であることを確認する方法はありません。また、関数テンプレートがインスタンス化されるときに、コンパイラは定義するかどうintかを認識します。floatoperator +
  • テンプレートはメタプログラミングへの扉を開きます(つまり、コンパイル時に物事を評価し、決定を下します):
    コンパイル時に、型が整数か浮動小数点かを知ることができます。ポインタであるか、定数修飾であるかなど...今後のc++0xの「型特性」を参照してください
  • クラステンプレートには部分的な特殊化があります
  • 関数テンプレートには明示的な完全な特殊化があります。この例では、マクロでは不可能な方法とはadd<float>(5, 3);異なる方法で実装できます。add<int>(5, 3);
  • マクロにはスコープがありません
  • #define min(i, j) (((i) < (j)) ? (i) : (j))-iおよびjパラメータは2回評価されます。たとえば、いずれかのパラメータにポストインクリメント変数がある場合、インクリメントは2回実行されます
  • マクロはプリプロセッサによって展開されるため、コンパイラのエラーメッセージは、マクロ定義自体ではなく、展開されたマクロを参照します。また、マクロはデバッグ中に展開された形式で表示されます
  • 等...

注:まれに、c ++ 0xが主流になるまで可変個引数テンプレートなどがないため、可変個引数マクロに依存することをお勧めします。 C++11はライブです。

参照:

于 2009-12-14T18:20:02.883 に答える
12

非常に基本的なレベルでは、はい、テンプレートは単なるマクロの置き換えです。しかし、そのように考えると、多くのことをスキップしています。

私の知る限り、マクロではシミュレートできないテンプレートの特殊化を検討してください。これにより、特定の型の特別な実装が可能になるだけでなく、テンプレートのメタプログラミングにおける重要な部分の 1 つになります。

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

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

これ自体は、できることのほんの一例です。テンプレート自体はチューリング完全です。

これは、スコープ、タイプセーフなどの非常に基本的なことを無視し、そのマクロはより厄介です。

于 2009-12-14T18:21:20.567 に答える
10

いいえ。簡単な反例の1つ:テンプレートは名前空間に従い、マクロは名前空間を無視します(プリプロセッサステートメントであるため)。

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
于 2009-12-14T18:20:11.177 に答える
9

C++ テンプレートは、すでに解析されたバージョンのコードで動作し、コンパイル時に任意のコードを生成できるという点で、(C マクロではなく) Lisp マクロに似ています。残念ながら、生のラムダ計算に似たものでプログラミングしているため、ループなどの高度な手法はやや面倒です。詳細については、Krysztof Czarnecki と Ulrich Eisenecker によるGenerative Programmingを参照してください。

于 2008-10-07T20:50:05.290 に答える
6

主題のより詳細な扱いを探している場合は、みんなのお気に入りの C++ ヘイターにあなたを向けることができます. この男は、私が夢にも思わなかったほど多くの C++ を知っていて、嫌っています。これは同時に、FQA を信じられないほど刺激的で優れたリソースにします。

于 2008-10-07T20:53:47.577 に答える
5

いいえ、できません。プリプロセッサは、Tのコンテナのようないくつかのことには(ほとんど)十分ではありませんが、テンプレートが実行できる他の多くのことには単に不十分です。

実際の例については、 AndreAlexandrescuによるModernC ++プログラミング、またはDaveAbrahamsとAlekseyGurtovoyによる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特定の名前が変数/値ではなく型を参照することを意図していることをコンパイラーに通知します。したがって、(たとえば)その型の他の変数を定義できます。

于 2009-12-14T18:24:03.040 に答える
5
  • テンプレートはタイプセーフです。
  • テンプレート化されたオブジェクト/型は、名前空間を作成したり、クラスのプライベート メンバーにすることができます。
  • テンプレート化された関数へのパラメーターは、関数本体全体で複製されません。

これらは本当に大したことであり、多数のバグを防ぎます。

于 2009-12-14T18:21:47.870 に答える
4

この回答は、Cプリプロセッサとジェネリックプログラミングにどのように使用できるかを明らかにすることを目的としています。


それらはいくつかの類似したセマンティクスを可能にするので、いくつかの点でそれらはあります。Cプリプロセッサは、一般的なデータ構造とアルゴリズムを有効にするために使用されています(トークンの連結を参照)。ただし、C ++テンプレートの他の機能を考慮しなくても、ジェネリックプログラミングゲーム全体を読んで実装するのに非常に便利です。

誰かがハードコアCだけのジェネリックプログラミングが実際に動作しているのを見たい場合は、libeventソースコードを読んでください-これもここで言及されています。コンテナ/アルゴリズムの膨大なコレクションが実装され、SINGLEヘッダーファイル(非常に読みやすい)で実行されます。私はこれを本当に尊敬しています。C++テンプレートコード(他の属性に好む)は非常に冗長です。

于 2009-12-14T18:23:34.713 に答える
4

言及されていないことは、テンプレート関数がパラメーターの型を推測できることです。

テンプレート <typename T>
ボイド関数(T t)
{
  T make_another = t;

今後の「typeof」演算子でそれを修正できると主張する人もいるかもしれませんが、それでも他のテンプレートを分解することはできません。

テンプレート <typename T>
void func(container<T> c)

あるいは:

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

また、専門科目が十分にカバーされていないと感じています。マクロで実行できないことの簡単な例を次に示します。

テンプレート <typename T>
T min(T a, TB)
{
  a < b を返しますか? a:b;
}

テンプレート <>
char* min(char* a, char* b)
{
  if (strcmp(a, b) < 0)
    を返します。
  そうしないと
    bを返します。
}

スペースが小さすぎて、型の専門化に入ることができませんが、私に関する限り、それでできることは驚異的です。

于 2008-10-07T23:09:58.973 に答える
3

原始的な例を試してみましょう。検討

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

として呼び出されます

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

もちろん、本当の違いはもっと深いですが、それはマクロとの類似点を捨てるのに十分なはずです。

編集:いいえ、マクロで型の安全性を確保することはできません。min()比較未満(つまり)を定義するすべてのタイプにtypesafeをどのように実装しますoperrator<か?

于 2009-12-14T18:26:47.497 に答える
2

テンプレートはタイプセーフです。定義を使用すると、コンパイルするコードを作成できますが、それでも正しく機能しません。

マクロは、コンパイラがコードに到達する前に展開されます。これは、拡張コードに対してエラーメッセージが表示され、デバッガーには拡張バージョンのみが表示されることを意味します。

マクロを使用すると、ある式が2回評価される可能性が常にあります。++xのようなものをパラメーターとして渡すことを想像してみてください。

于 2008-10-07T20:48:49.427 に答える
2

テンプレートは、名前空間に配置することも、クラスのメンバーにすることもできます。マクロは単なる前処理ステップです。基本的に、テンプレートは、他のすべてのものとうまく機能する(より良い?)言語のファーストクラスのメンバーです。

于 2008-10-07T20:48:49.537 に答える
2

typenameキーワードは、文脈自由のネストされたtypdefを有効にするために提示されます。これらは、メタデータを型(特にポインターなどの組み込み型)に追加できるトレイト手法に必要でした。これは、STLを作成するために必要でした。typenameキーワードは、それ以外はclassキーワードと同じです。

于 2009-12-14T18:25:14.103 に答える
2

テンプレートはデータ型を理解します。 マクロはそうではありません。

これは、次のようなことができることを意味します...

さらに、テンプレートはタイプ セーフであるため、いくつかの仮想的な高度なプリプロセッサで実行できる可能性があるテンプレート コーディング手法がいくつかありますが、せいぜい厄介でエラーが発生しやすいものです (例:テンプレート テンプレート パラメーター、デフォルト テンプレート引数、ポリシー テンプレート as最新の C++ 設計で説明されています)。

于 2009-12-14T18:33:51.523 に答える
2

私の意見では、マクロは 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の情報をご覧ください。テンプレートを使用して、使用される特性と呼ばれるものを実行できます。これには、タイプなどの一種のドキュメントがあります。テンプレートを使用すると、マクロでできることよりもはるかに多くのことができます。

于 2009-09-08T19:11:32.863 に答える
2

テンプレートは、最も基本的な機能がマクロに似ているだけです。結局のところ、テンプレートはマクロの「文明化された」代替手段として言語に導入されました。しかし、その最も基本的な機能に関しても、類似点は表面的なものにすぎません。

ただし、特殊化 (部分的または明示的) など、テンプレートのより高度な機能に到達すると、マクロとの明らかな類似性は完全になくなります。

于 2009-12-14T18:36:16.463 に答える
2

マクロにはいくつかの基本的な問題があります。

まず、スコープやタイプを尊重しません。を持っている場合、プログラムに#define max(a, b)...トークンがあるときはいつでもmax、何らかの理由で置き換えられます。変数名であるか、ネストされたスコープの奥深くにある場合は置き換えられます。これにより、見つけにくいコンパイル エラーが発生する可能性があります。対照的に、テンプレートは C++ 型システム内で機能します。テンプレート関数はスコープ内でその名前を再利用することができ、変数名を書き換えようとしません。

第二に、マクロは変更できません。テンプレートstd::swapは通常、一時変数を宣言し、明らかな代入を行うだけです。それは、マクロが制限されるものです。これは大きなベクトルにとって非常に非効率的であるため、ベクトルには、swapコンテンツ全体ではなく参照を交換する特別なものがあります。(これは、平均的な C++ プログラマーが書くべきではないが使用するものにおいて非常に重要であることが判明しました。)

第 3 に、マクロはいかなる形式の型推論も行うことができません。型の変数を宣言する必要があり、型が何であるかがわからないため、そもそもジェネリック swap マクロを作成することはできません。テンプレートは型を認識します。

テンプレートの力を示す好例の 1 つは、もともと Standard Template Library と呼ばれていたものです。これは、コンテナー、アルゴリズム、イテレーターとして標準に含まれています。それらがどのように機能するかを見て、マクロに置き換える方法を考えてみてください。Alexander Stepanov は、STL のアイデアを実装するためにさまざまな言語を検討し、テンプレートを使用した C++ だけが動作する言語であると結論付けました。

于 2009-12-14T21:47:56.300 に答える
2

テンプレート パラメーターは型チェックされ、テンプレートにはマクロよりも多くの利点がありますが、テンプレートは依然としてテキスト置換に基づいているという点でマクロに非常に似ています。コンパイラは、置換する型パラメーターを指定するまで、テンプレート コードが意味をなすことを確認しません。実際に呼び出さない限り、Visual C++ はこの関数について文句を言いません。

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

編集:この例は、Visual C++ にのみ適用されます。標準C++では、テンプレート コードは、テンプレートが使用される前に実際に構文ツリーに解析されるため、この例は VC++ では受け入れられますが、GCC や Clang では受け入れられません。(これは、VC++ コードを GCC に移植しようとして、専門化されていないテンプレートで何百もの構文エラーに対処しなければならなかったときに学んだことです。) しかし、構文ツリーは依然として意味的に意味があるとは限りません。コンパイラに関係なく、<template arguments>.

したがって、一般に、テンプレートが受け入れるように設計されている型パラメーターの特定のカテゴリに対して、テンプレート コードが正しく機能するかどうか、または正常にコンパイルされるかどうかを知ることは不可能です。

于 2009-03-06T17:50:34.313 に答える
2

テンプレートは、マクロ プリプロセッサよりもはるかに多くのことができます。

たとえば、テンプレートの特殊化があります。このテンプレートがこの型または定数でインスタンス化されている場合、デフォルトの実装を使用しないでください。ただし、これはここにあります...

... テンプレートは、いくつかのパラメータが同じ型であることを強制できます...


ここにあなたが見たいと思うかもしれないいくつかの情報源があります:

  • Vandervoorde と Jossutis によるC++ テンプレート。これは、私が知っているテンプレートに関する最良かつ最も完全な本です。
  • ブースト ライブラリは、ほぼ完全にテンプレート定義で構成されています。
于 2008-10-07T21:11:05.997 に答える
2

これは、すでに述べた回答の結果としての回答ではありません。

科学者、外科医、グラフィック アーティストなど、プログラミングが必要な人たち (ただし、プロのフルタイム ソフトウェア開発者になることはありません) と一緒に仕事をしているのですが、マクロは時折プログラマーが簡単に理解できることがわかります。 C++ でのプログラミングを深く継続的に経験することによってのみ可能なレベルの抽象的思考。テンプレートが有用な概念であるコードを使用する場合、その概念が使用に十分な意味を持つようになるには、多くのインスタンスが必要です。これはどの言語機能についても言えますが、テンプレートの経験量は、スペシャリストのカジュアル プログラマーが日常業務から得る可能性が高いものよりも大きなギャップを示します。

平均的な天文学者や電子技術者は、おそらくマクロを問題なく理解しており、マクロを避けるべき理由を理解しているかもしれませんが、日常的に使用するのに十分なほどテンプレートを理解していません。そのコンテキストでは、実際にはマクロの方が優れています。当然のことながら、多くの例外が存在します。一部の物理学者は、プロのソフトウェア エンジニアの周りを回っていますが、これは一般的ではありません。

于 2008-10-08T00:57:04.433 に答える
0

テンプレートは、ある程度の型安全性を提供します。

于 2008-10-07T20:46:51.023 に答える
0

テンプレートは言語に統合されており、タイプ セーフです。

マクロでこれを行う方法を教えてください。これは重いテンプレート メタプログラミングです。

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

私の知る限り、マクロは、テンプレートの部分的な特殊化ができる方法で型を計算することはできないと思います。

于 2014-10-08T10:06:36.660 に答える