私はそれらがC / C ++で非常に安全に実装されていないことを知っています。より安全な方法で実装することはできませんか?マクロの不利な点は、マクロが提供する強力な力を上回るほどひどいものですか?
WithEvents
修飾子に対応する機能をどのように作成できますか?セマンティックマクロのようなものが必要です。
私はそれらがC / C ++で非常に安全に実装されていないことを知っています。より安全な方法で実装することはできませんか?マクロの不利な点は、マクロが提供する強力な力を上回るほどひどいものですか?
WithEvents
修飾子に対応する機能をどのように作成できますか?セマンティックマクロのようなものが必要です。
回答:
主な理由は、マクロがレキシカルだからだと思います。これにはいくつかの結果があります。
コンパイラには、マクロが意味的に閉じていること、つまり、関数のように「意味の単位」を表していることをチェックする方法がありません。(考慮#define TWO 1+1
— TWO*TWO
同等のものは何ですか?3.)
マクロは関数のように型付けされていません。コンパイラは、パラメータと戻り値の型が意味をなすことを確認できません。マクロを使用する展開式のみをチェックできます。
コードがコンパイルされない場合、コンパイラは、エラーがマクロ自体にあるのか、マクロが使用されている場所にあるのかを知る方法がありません。コンパイラは、半分の時間で間違った場所を報告するか、おそらくどちらかが正常であっても両方を報告する必要があります。(考えてみましょう#define min(x,y) (((x)<(y))?(x):(y))
:の種類の場合、コンパイラは何をすべきx
とはy
一致していないか、実装していませんoperator<
?)
自動化ツールは、意味的に有用な方法でそれらと連携することはできません。特に、関数のように機能するが式に展開されるマクロ用のIntelliSenseのようなものを持つことはできません。(再び、min
例。)
マクロの副作用は、関数の場合ほど明確ではないため、プログラマに混乱を招く可能性があります。(min
例をもう一度検討してください:関数呼び出しでx
は、式for が1回しか評価されないことがわかりますが、ここではマクロを見ずに知ることができません。)
私が言ったように、これらはすべてマクロが字句的であるという事実の結果です。それらをより適切なものに変えようとすると、関数と定数になります。
しかし、はい、マクロは C / C ++よりも優れた設計と実装が可能です。
マクロの問題は、マクロが事実上、コードを別のものに書き換える言語構文拡張メカニズムであることです。
C / C ++の場合、基本的な健全性チェックはありません。注意すれば、問題はありません。間違えたり、マクロを使いすぎたりすると、大きな問題が発生する可能性があります。
これに加えて、(C / C ++スタイルの)マクロでできる多くの簡単なことは、他の言語で他の方法で行うことができます。
さまざまなLisp方言のような他の言語では、マクロはコア言語構文とよりよく統合されますが、マクロの「漏れ」の宣言で問題が発生する可能性があります。これは、衛生的なマクロによって対処されます。
マクロ(マクロ命令の略)は、アセンブリ言語のコンテキストで最初に登場しました。ウィキペディアによると、マクロは1950年代に一部のIBMアセンブラーで利用可能でした。
元のLISPにはマクロはありませんでしたが、1960年代半ばに初めてMacLispに導入されました:https : //stackoverflow.com/questions/3065606/when-did-the-idea-of-macros-user-defined-code -transformation-appear。http://www.csee.umbc.edu/courses/331/resources/papers/Evolution-of-Lisp.pdf。それ以前は、「fexprs」はマクロのような機能を提供していました。
Cの初期バージョンにはマクロがありませんでした(http://cm.bell-labs.com/cm/cs/who/dmr/chist.html)。これらは、プリプロセッサを介して1972-73年頃に追加されました。それ以前は、Cはとのみをサポート#include
していました#define
。
M4マクロプリプロセッサは1977年頃に誕生しました。
より最近の言語では、動作のモデルがテキストではなく構文であるマクロを実装しているようです。
したがって、誰かが「マクロ」という用語の特定の定義の優位性について話すとき、その意味は時間とともに進化していることに注意することが重要です。
スコットが指摘しているように、マクロはロジックを隠すことができます。もちろん、関数、クラス、ライブラリ、その他多くの一般的なデバイスも同様です。
しかし、強力なマクロシステムをさらに活用して、通常は言語にない構文と構造を設計および利用することができます。これは確かに素晴らしいツールです。ドメイン固有の言語、コードジェネレーターなど、すべて単一の言語環境で快適に...
ただし、悪用される可能性があります。コードの読み取り、理解、デバッグが難しくなり、新しいプログラマーがコードベースに慣れるのに必要な時間が長くなり、費用のかかるミスや遅延が発生する可能性があります。
そのため、プログラミングを単純化することを目的とした言語(JavaやPythonなど)にとって、そのようなシステムは忌み嫌われるものです。
マクロは、状況によっては非常に安全に実装できます。たとえば、Lispでは、マクロは変換されたコードをデータ構造(s-expression)として返す関数にすぎません。もちろん、Lispはそれがホモイコニックであるという事実と「コードはデータである」という事実から大きな恩恵を受けます。
簡単なマクロの例は、例外の場合に使用されるデフォルト値を指定する次のClojureの例です。
(defmacro on-error [default-value code]
`(try ~code (catch Exception ~'e ~default-value)))
(on-error 0 (+ nil nil)) ;; would normally throw NullPointerException
=> 0 ;l; but we get the default value
しかしLispでも、一般的なアドバイスは「必要がない限りマクロを使用しない」です。
ホモイコニック言語を使用していない場合、マクロはより巧妙になり、他のさまざまなオプションにはすべて落とし穴があります。
さらに、マクロができることはすべて、最終的に他の方法でチューリング完全な言語で実現できます(これが多くの定型文を書くことを意味する場合でも)。このすべてのトリッキーな結果として、多くの言語がマクロは実装するすべての努力の価値がないと判断することは驚くことではありません。
質問に答えるには、どのマクロが主に使用されているかを考えてください(警告:脳でコンパイルされたコード)。
#define X 100
これは次のものに簡単に置き換えることができます。 const int X = 100;
#define max(X,Y) (X>Y?X:Y)
関数のオーバーロードをサポートする言語では、正しい型の関数をオーバーロードすることで、またはジェネリックをサポートする言語でジェネリック関数を使用することで、はるかにタイプセーフな方法でエミュレートできます。このマクロは、コンパイルされる可能性のあるポインタや文字列を含むすべてのものを喜んで比較しようとしますが、ほとんど間違いなくあなたが望んでいたものではありません。一方、マクロをタイプセーフにした場合、マクロはオーバーロードされた関数に比べて利点も利便性も提供しません。
#define p printf
これはp()
、同じことをする関数に簡単に置き換えられます。これはC(va_arg()
関数ファミリーを使用する必要があります)に非常に関係していますが、可変数の関数引数をサポートする他の多くの言語では、はるかに簡単です。
特別なマクロ言語ではなく言語内でこれらの機能をサポートすることは、コードを読んでいる他の人にとってよりシンプルで、エラーが少なく、混乱がはるかに少ないです。実際、別の方法で簡単に複製できないマクロの単一のユースケースを考えることはできません。唯一の彼らはのような条件付きコンパイル構文に結びついているとき、マクロは本当に便利です場所です#if
(など)。
その点については、一般的な言語での条件付きコンパイルに対する非プリプロセッサソリューションは(Javaでのバイトコードインジェクションのように)非常に面倒だと思うので、私はあなたと議論しません。しかし、Dのような言語は、プリプロセッサを必要とせず、プリプロセッサの条件を使用するよりも面倒ではなく、エラーがはるかに少ないソリューションを考え出しました。
In fact, I can't think of a single use-case for macros that can't easily be duplicated in another way
:少なくともCでは、マクロを使用せずにトークン連結を使用して識別子(変数名)を形成することはできません。
enum
、条件を定義してメッセージを定義する定数文字列配列を使用しようとすると、問題が発生する可能性があります列挙型と配列が同期しなくなります。マクロを使用してすべての(enum、string)ペアを定義し、そのマクロをスコープ内の他の適切な定義とともに2回含めると、各列挙値を文字列の隣に配置できます。
マクロで私が見た最大の問題は、それらを頻繁に使用すると、見つけるのが簡単な場合とそうでない場合があるマクロのロジックを隠すことができるため、コードの読み取りと保守が非常に難しくなることです)。
C / C ++のMACROは非常に限定的で、エラーが発生しやすく、実際にはそれほど有用ではないことに注意してください。
LISPやz / OSアセンブラー言語などで実装されているMACROは、信頼性が高く、非常に便利です。
しかし、Cの限られた機能の悪用のため、彼らは悪い評判を集めました。だから、誰もマクロを実装しなくなり、代わりに、テンプレートのように使用される単純なもののマクロを実行し、Javaのアノテーションのような、より複雑なもののマクロを実行します。