私はC++で数年間プログラミングしており、STLをかなり使用していて、独自のテンプレートクラスを数回作成して、その方法を確認しています。
OOデザインにテンプレートをより深く統合しようとしています。そして、しつこい考えが私に戻ってきます:それらは単なるマクロです、本当に...あなたは実装することができます(むしろ醜い) )本当に必要な場合は、#definesを使用してauto_ptrs。
このテンプレートの考え方は、コードが実際にどのように機能するかを理解するのに役立ちますが、どういうわけかポイントを逃しているに違いないと感じています。マクロは悪の転生を意味しますが、「テンプレートメタプログラミング」は大流行です。
それで、本当の違いは何ですか?そして、テンプレートはどのようにして#defineがあなたを危険にさらすのを避けることができますか?
マクロはテキスト置換メカニズムです。
テンプレートは、コンパイル時に実行される機能的なチューリング完全言語であり、C++型システムに統合されています。これらは言語のプラグインメカニズムと考えることができます。
マクロとテンプレートを区別しようとするコメントがたくさんあります。
はい。どちらも同じです。コード生成ツールです。
マクロは原始的な形式であり、コンパイラーによる強制はあまりありません(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以上のパラメーター(参照が必要)を使用してこの種のテンプレートを定義し、それぞれが異なる物理ユニットタイプを表しているようです。あなたの数のためだけに、そのようなクラスのタイプセーフについて考えてください。
これらはコンパイラーによって解析され、beforeを実行するプリプロセッサーでは解析されませんコンパイラ。
MSDNがそれについて言っているのは、次のとおりです。 http://msdn.Microsoft.com/en-us/library/aa903548(VS.71).aspx
マクロに関するいくつかの問題は次のとおりです。
- マクロパラメータが互換性のある型であることをコンパイラが確認する方法はありません。
- マクロは、特別な型チェックなしで展開されます。
- Iパラメータとjパラメータは2回評価されます。たとえば、いずれかのパラメータにポストインクリメントされた変数がある場合、インクリメントは2回実行されます。
- マクロはプリプロセッサによって展開されるため、コンパイラエラーメッセージは、マクロ定義自体ではなく、展開されたマクロを参照します。また、マクロはデバッグ中に展開された形式で表示されます。
それで足りないのなら、何なのかわかりません。
答えはとても長いので、すべてを要約することはできませんが、
int
またはfloat
define _operator +
_add<float>(5, 3);
はadd<int>(5, 3);
とは異なる方法で実装できますが、これはマクロでは不可能です#define min(i, j) (((i) < (j)) ? (i) : (j))
-i
およびj
パラメーターは2回評価されます。たとえば、いずれかのパラメータにポストインクリメントされた変数がある場合、インクリメントは2回実行されます注:まれに、c ++ 0xが主流になるまで可変テンプレートのようなものは存在しないため、可変マクロに依存する方が好ましい場合がありました。C++ 11 はライブです。
参照:
非常に基本的なレベルでは、はい、テンプレートは単なるマクロの置き換えです。しかし、あなたはそれをそのように考えることで、たくさんのものをスキップしています。
テンプレートの特殊化を検討してください。これは私の知る限り、マクロではシミュレートできません。これにより、特定の型の特別な実装が可能になるだけでなく、テンプレートメタプログラミングの重要な部分の1つになります。
template <typename T>
struct is_void
{
static const bool value = false;
}
template <>
struct is_void<void>
{
static const bool value = true;
}
それ自体は あなたができる多くのこと のほんの一例です。テンプレート自体はチューリング完全です。
これは、スコープ、タイプセーフなどの非常に基本的なものを無視し、そのマクロは厄介です。
[〜#〜]いいえ[〜#〜]。単純な反例の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
C++テンプレートは、LISPマクロ(Cマクロではない)のようなもので、既に解析されたバージョンのコードで動作し、コンパイル時に任意のコードを生成できます。残念ながら、あなたは生のラムダ計算に似たものでプログラミングしているので、ループのような高度なテクニックはちょっと面倒です。すべての悲惨な詳細については、Krysztof CzarneckiとUlrich EiseneckerによるGenerative Programmingを参照してください。
あなたが主題のより詳細な扱いを探しているなら、私はあなたをみんなの お気に入りのC++嫌い に変えることができます。この男は、私が夢見た以上に多くのC++を知っており、嫌っています。これは同時にFQAを非常に刺激的で優れたリソースにします。
これらは本当に大変なことであり、多くのバグを防ぎます。
いいえ、それは不可能です。プリプロセッサは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
は、特定の名前が変数/値ではなく型を参照することを目的としているため、(たとえば)その型の他の変数を定義できることをコンパイラーに伝えます。
言及されていないことの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; }
スペースが小さすぎて型の特殊化に入ることができませんが、私が関係する限り、これで何ができるかは驚くべきことです。
この回答は、Cプリプロセッサと、それを一般的なプログラミングに使用する方法を明らかにすることを目的としています
それらはいくつかの同様の意味論を可能にするので、いくつかの点でです。 Cプリプロセッサは、一般的なデータ構造とアルゴリズムを有効にするために使用されています( トークン連結 を参照)。ただし、C++テンプレートの他の機能を考慮せずに、汎用プログラミングゲーム全体をLOT CLEARERにして、実装します。
誰かがハードコアCのみの一般的なプログラミングの動作を確認したい場合は、 libevent ソースコードを読んでください-これも言及されています here 。コンテナ/アルゴリズムの膨大なコレクションが実装されており、[〜#〜] single [〜#〜]ヘッダーファイルで実行されます(非常に読みやすい)。私はこれに本当に感心します。C++テンプレートコード(他の属性に好む)は非常に冗長です。
Typenameキーワードは、コンテキストフリーのネストされたtypdefを有効にするために提供されています。これらは、メタデータを型(特にポインターなどの組み込み型)に追加できる特性手法に必要でしたが、これはSTLを記述するために必要でした。それ以外のtypenameキーワードは、classキーワードと同じです。
プリミティブな例を試してみましょう。検討する
_#define min(a,b) ((a)<(b))?(a):(b)
_
として呼び出された
_c = min(a++,++b);
_
もちろん、実際の違いはより深いですが、マクロとの類似点を破棄するにはそれで十分です。
編集:いいえ、マクロではタイプセーフを保証できません。比較未満(つまり_operrator<
_)を定義するすべての型に対して、タイプセーフmin()
をどのように実装しますか?
テンプレートはデータ型を理解します。マクロは理解しません。
つまり、次のようなことができます...
さらに、テンプレートはタイプセーフであるため、いくつかの仮説的な高度なプリプロセッサで実行できると思われるが、せいぜい粗末でエラーが発生しやすいテンプレートコーディングテクニックがいくつかあります(例: template template parameters 、デフォルトのテンプレート引数、で説明されているポリシーテンプレート 最新のC++デザイン)。
テンプレートは、最も基本的な機能がマクロに似ているだけです。結局のところ、テンプレートはマクロの「文明化された」代替手段として言語に導入されました。しかし、その最も基本的な機能に関しても、類似点は肌の深さだけです。
ただし、特殊化(部分的または明示的)など、テンプレートのより高度な機能に到達すると、マクロとの明らかな類似性は完全になくなります。
私の意見では、マクロは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からの情報 をご覧ください。テンプレートを使用して、タイプなどのドキュメントの一種が使用されている、いわゆる特性を実行できます。テンプレートを使用すると、マクロで可能なことよりもはるかに多くのことができます。
テンプレートはタイプセーフです。 defineを使用すると、コンパイルするコードを使用できますが、それでも正しく機能しません。
マクロは、コンパイラーがコードに到達する前に展開されます。つまり、拡張コードのエラーメッセージが表示され、デバッガーは拡張バージョンのみを認識します。
マクロを使用すると、一部の式が2回評価される可能性が常にあります。 ++ xのようなものをパラメーターとして渡すことを想像してみてください。
テンプレートは名前空間に配置するか、クラスのメンバーにすることができます。マクロは前処理のステップにすぎません。基本的に、テンプレートは、他のすべてでニース(ナイス?)を演じる言語のファーストクラスメンバーです。
テンプレートパラメータは型チェックされており、テンプレートにはマクロよりも多くの利点がありますが、テンプレートは依然としてテキスト置換に基づいているという点でマクロに非常によく似ています。コンパイラは、置換する型パラメータを指定するまで、テンプレートコードが有効であることを確認しません。 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>
を指定してテンプレートをインスタンス化するまで、本文の型チェックは行われません。
したがって、一般に、テンプレートが受け入れるように設計されている型パラメーターの特定のカテゴリについて、テンプレートコードが正しく機能するか、または正常にコンパイルされるかを知ることは不可能です。
これは、すでに述べた回答の結果としての回答ではありません。
科学者、外科医、グラフィックアーティスト、およびプログラムを必要とする他の人たちとの共同作業-専門のフルタイムソフトウェア開発者ではない、またはそうなることはありません-マクロはたまにプログラマーによって簡単に理解されますが、テンプレートにはより高いものが必要なようです抽象的思考のレベルは、C++でプログラミングを深く深く継続的に経験した場合にのみ可能です。テンプレートが有用な概念であるコードを操作する多くのインスタンスが必要です。概念が使用するのに十分な意味を持つためです。これはどの言語機能についても言えることですが、テンプレートの経験量は、専門のカジュアルプログラマーが日常の作業から得る可能性が高いギャップよりも大きくなります。
平均的な天文学者や電子工学のエンジニアは、おそらくマクロをうまく処理し、マクロを避けなければならない理由を理解しているかもしれませんが、テンプレートを日常的に使用するには十分ではありません。その文脈では、マクロは実際に優れています。当然、例外はたくさんあります。一部の物理学者はプロソフトウェアエンジニアの周りを回っていますが、これは一般的ではありません。
マクロにはいくつかの基本的な問題があります。
まず、スコープやタイプを尊重しません。 #define max(a, b)...
がある場合、プログラムにトークンmax
があると、何らかの理由でトークンが置き換えられます。変数名である場合、またはネストされたスコープ内の深い場合は置き換えられます。これにより、見つけにくいコンパイルエラーが発生する可能性があります。対照的に、テンプレートはC++型システム内で機能します。テンプレート関数は、スコープ内でその名前を再利用でき、変数名を書き換えようとはしません。
次に、マクロを変更することはできません。テンプレートstd::swap
は通常、一時変数を宣言し、明らかな代入を行います。これは、それが通常機能する明白な方法だからです。これがマクロの制限です。これは大きなベクトルに対しては非常に非効率的であり、ベクトルにはコンテンツ全体ではなく参照をスワップする特別なswap
があります。 (これは、平均的なC++プログラマーが書くべきではないが、実際に使用するものでは非常に重要であることがわかります。)
第3に、マクロは型推論のいかなる形式も実行できません。型の変数を宣言する必要があり、型が何であるかがわからないため、最初に汎用のスワップマクロを記述することはできません。テンプレートはタイプに対応しています。
テンプレートの威力の1つの素晴らしい例は、元々は標準テンプレートライブラリと呼ばれていたもので、コンテナ、アルゴリズム、イテレータとして標準に含まれています。それらがどのように機能するかを見て、それをマクロに置き換える方法を考えてみてください。 Alexander Stepanovは、STLのアイデアを実装するために多種多様な言語を調査し、テンプレートを使用したC++だけが機能するものであると結論付けました。
テンプレートはある程度のタイプセーフを提供します。
テンプレートは言語に統合されており、タイプセーフです。
マクロでこれを行う方法を教えてください。これは重いテンプレートのメタプログラミングです。
https://www.youtube.com/watch?v=0A9pYr8wevk
マクロAFAIKは、テンプレートの部分的な特殊化でできる方法では型を計算できないと思います。