web-dev-qa-db-ja.com

C ++テンプレートは変装したマクロにすぎませんか?

私はC++で数年間プログラミングしており、STLをかなり使用していて、独自のテンプレートクラスを数回作成して、その方法を確認しています。

OOデザインにテンプレートをより深く統合しようとしています。そして、しつこい考えが私に戻ってきます:それらは単なるマクロです、本当に...あなたは実装することができます(むしろ醜い) )本当に必要な場合は、#definesを使用してauto_ptrs。

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

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

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

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

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

50
Ferruccio

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

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

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

31
Jeff B

これらはコンパイラーによって解析され、beforeを実行するプリプロセッサーでは解析されませんコンパイラ。

MSDNがそれについて言っているのは、次のとおりです。 http://msdn.Microsoft.com/en-us/library/aa903548(VS.71).aspx

マクロに関するいくつかの問題は次のとおりです。

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

それで足りないのなら、何なのかわかりません。

30
rlerallut

答えはとても長いので、すべてを要約することはできませんが、

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

注:まれに、c ++ 0xが主流になるまで可変テンプレートのようなものは存在しないため、可変マクロに依存する方が好ましい場合がありました。C++ 11 はライブです。

参照:

22
Gregory Pakosz

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

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

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

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

それ自体は あなたができる多くのこと のほんの一例です。テンプレート自体はチューリング完全です。

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

12
GManNickG

[〜#〜]いいえ[〜#〜]。単純な反例の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
10
catchmeifyoutry

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

9
Andru Luvisi

あなたが主題のより詳細な扱いを探しているなら、私はあなたをみんなの お気に入りのC++嫌い に変えることができます。この男は、私が夢見た以上に多くのC++を知っており、嫌っています。これは同時にFQAを非常に刺激的で優れたリソースにします。

6
Ryan
  • テンプレートはタイプセーフです。
  • テンプレート化されたオブジェクト/タイプは、名前空間を設定したり、クラスのプライベートメンバーにすることができます。
  • テンプレート化された関数へのパラメーターは、関数本体全体に複製されません。

これらは本当に大変なことであり、多くのバグを防ぎます。

5
moonshadow

いいえ、それは不可能です。プリプロセッサはTのコンテナーのようないくつかのことには(かろうじて)十分ですが、テンプレートが行うことができる他のいくつかのことには単に不十分です。

実際の例については、Andre AlexandrescuによるModern C++ Programming、またはDave AbrahamsとAleksey GurtovoyによるC++ 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は、特定の名前が変数/値ではなく型を参照することを目的としているため、(たとえば)その型の他の変数を定義できることをコンパイラーに伝えます。

4
Jerry Coffin

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

テンプレート<タイプ名T> 
 void func(T t)
 {
 T make_another = t; 

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

テンプレート<タイプ名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; 
} 
 
テンプレート<> 
 char * min(char * a、char * b)
 {
 if(strcmp(a、b)<0)
 return a; 
 else 
 return b; 
} 

スペースが小さすぎて型の特殊化に入ることができませんが、私が関係する限り、これで何ができるかは驚くべきことです。

4
keraba

この回答は、Cプリプロセッサと、それを一般的なプログラミングに使用する方法を明らかにすることを目的としています


それらはいくつかの同様の意味論を可能にするので、いくつかの点でです。 Cプリプロセッサは、一般的なデータ構造とアルゴリズムを有効にするために使用されています( トークン連結 を参照)。ただし、C++テンプレートの他の機能を考慮せずに、汎用プログラミングゲーム全体をLOT CLEARERにして、実装します。

誰かがハードコアCのみの一般的なプログラミングの動作を確認したい場合は、 libevent ソースコードを読んでください-これも言及されています here 。コンテナ/アルゴリズムの膨大なコレクションが実装されており、[〜#〜] single [〜#〜]ヘッダーファイルで実行されます(非常に読みやすい)。私はこれに本当に感心します。C++テンプレートコード(他の属性に好む)は非常に冗長です。

4
Hassan Syed

Typenameキーワードは、コンテキストフリーのネストされたtypdefを有効にするために提供されています。これらは、メタデータを型(特にポインターなどの組み込み型)に追加できる特性手法に必要でしたが、これはSTLを記述するために必要でした。それ以外のtypenameキーワードは、classキーワードと同じです。

2
Hassan Syed

プリミティブな例を試してみましょう。検討する

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

として呼び出された

_c = min(a++,++b);
_

もちろん、実際の違いはより深いですが、マクロとの類似点を破棄するにはそれで十分です。

編集:いいえ、マクロではタイプセーフを保証できません。比較未満(つまり_operrator<_)を定義するすべての型に対して、タイプセーフmin()をどのように実装しますか?

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

つまり、次のようなことができます...

  • 任意のデータ型を取ることができる操作(例: wrapping numbers )を定義し、データ型が整数か浮動小数点かに基づいて適切なアルゴリズムを選択する特殊化を提供します
  • コンパイル時にデータ型の側面を決定し、 テンプレートサイズの配列サイズの推定 のようなトリックを許可します。これは、Microsoftが strcpy_s とそのilkのC++オーバーロードに使用します。

さらに、テンプレートはタイプセーフであるため、いくつかの仮説的な高度なプリプロセッサで実行できると思われるが、せいぜい粗末でエラーが発生しやすいテンプレートコーディングテクニックがいくつかあります(例: template template parameters 、デフォルトのテンプレート引数、で説明されているポリシーテンプレート 最新のC++デザイン)。

2
Josh Kelley

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

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

2
AnT

私の意見では、マクロは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
Partial

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

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

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

2
Milan Babuškov

テンプレートは名前空間に配置するか、クラスのメンバーにすることができます。マクロは前処理のステップにすぎません。基本的に、テンプレートは、他のすべてでニース(ナイス?)を演じる言語のファーストクラスメンバーです。

2
user4891

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

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

...テンプレートは、一部のパラメータが同じタイプであることを強制できます。


ここにいくつかの情報源があります。

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

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

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

編集:この例は、Visual C++にのみ適用されます。 standardC++では、テンプレートコードが実際に構文ツリーに解析されてから、テンプレートが使用されるため、この例はVC++では受け入れられますが、GCCまたはClangでは受け入れられません。 。 (VC++コードをGCCに移植しようとしたときに私がこれを学び、専門外のテンプレートで何百もの構文エラーに対処する必要がありました。)ただし、構文ツリーは必ずしも意味をなさない。コンパイラーに関係なく、<template arguments>を指定してテンプレートをインスタンス化するまで、本文の型チェックは行われません。

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

2
Qwertie

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

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

平均的な天文学者や電子工学のエンジニアは、おそらくマクロをうまく処理し、マクロを避けなければならない理由を理解しているかもしれませんが、テンプレートを日常的に使用するには十分ではありません。その文脈では、マクロは実際に優れています。当然、例外はたくさんあります。一部の物理学者はプロソフトウェアエンジニアの周りを回っていますが、これは一般的ではありません。

1
DarenW

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

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

次に、マクロを変更することはできません。テンプレートstd::swapは通常、一時変数を宣言し、明らかな代入を行います。これは、それが通常機能する明白な方法だからです。これがマクロの制限です。これは大きなベクトルに対しては非常に非効率的であり、ベクトルにはコンテンツ全体ではなく参照をスワップする特別なswapがあります。 (これは、平均的なC++プログラマーが書くべきではないが、実際に使用するものでは非常に重要であることがわかります。)

第3に、マクロは型推論のいかなる形式も実行できません。型の変数を宣言する必要があり、型が何であるかがわからないため、最初に汎用のスワップマクロを記述することはできません。テンプレートはタイプに対応しています。

テンプレートの威力の1つの素晴らしい例は、元々は標準テンプレートライブラリと呼ばれていたもので、コンテナ、アルゴリズム、イテレータとして標準に含まれています。それらがどのように機能するかを見て、それをマクロに置き換える方法を考えてみてください。 Alexander Stepanovは、STLのアイデアを実装するために多種多様な言語を調査し、テンプレートを使用したC++だけが機能するものであると結論付けました。

1
David Thornley

テンプレートはある程度のタイプセーフを提供します。

0
tpower

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

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

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

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

0
Germán Diago