2

マクロとその可読性について議論しました。場合によっては、マクロを使用するとコードが短くなり、理解しやすくなり、読むのに疲れにくくなると思います。

例えば:

#include <iostream>

#define EXIT_ON_FAILURE(s) if(s != 0) {std::cout << "Exited on line " << __LINE__ << std::endl; exit(1);}

inline void exitOnFailure(int s, int lineNum) {
    if (s != 0) {
        std::cout << "Exited on line " << lineNum << std::endl; 
        exit(1);
    }
}

int foo() {
    return 1;
}

int bar(int a, int b, int c) {
    return 0;
}

int main() {
    // first option
    if (foo() != 0) {
        std::cout << "Exited on line " << __LINE__ << std::endl;
        exit(1);    
    }
    if (bar(1, 2, 3) != 0) {
        std::cout << "Exited on line " << __LINE__ << std::endl;
        exit(1);    
    }

    // second option
    EXIT_ON_FAILURE(foo());
    EXIT_ON_FAILURE(bar(1, 2, 3));

    // third option
    exitOnFailure(foo(), __LINE__);
    exitOnFailure(bar(1, 2, 3), __LINE__);

    return 0;
}

ここでは 2 番目のオプションを好みます。これは短くてコンパクトであり、Caps Lock のテキストがキャメル ケースよりも明確で読みやすいためです。

この方法、特に C++ に何か問題がありますか?それとも単にスタイルが悪い (しかし受け入れられる) だけですか?

4

3 に答える 3

3

マクロは、C/C++ の非常に強力な機能であり、すべての C 機能と同様に、デフォルトで足元に向けられています。マクロの次の使用法を検討してください。

if (doSomething())
    EXIT_ON_FAILURE(s)   /* <-- MISSING SEMICOLON! OH NOES!!! */
else
    doSomethingElse();

はステートメント内の にelse属しますか、それとも 展開によって作成されたですか? いずれにせよ、セミコロンが 1 つ欠けているという動作はまったく予想外です。EXIT_ON_FAILURE() が関数の場合、コンパイラ エラーが発生します。この場合、コンパイルはできますが、間違ったことをするコードが得られます。ififEXIT_ON_FAILURE

それがマクロの問題です。関数や変数のように見えますが、そうではありません。不適切に作成されたマクロは、与え続ける贈り物です。マクロを使用するたびに微妙なバグが発生する可能性があり、マクロを変更するたびに、触れていないコードに論理エラーが発生する恐れがあります。

一般に、どうしても必要な場合を除き、マクロは避けるべきです。

定数を定義する必要がある場合は、const変数またはenum. 優れたコンパイラ (無料で入手できます) は、#define された定数と同じように、結果の実行可能ファイルでそれらをリテラルに変換しますが、期待どおりに型変換を処理し、デバッガのシンボル テーブルに表示されます。

インライン関数のようなものが必要な場合は、インライン関数を使用してください。C++ と C99 の両方がそれらを提供し、ほとんどの適切なコンパイラ (無料のものを含む) は拡張機能として長い間それを行ってきました。

関数は、マクロとは異なり、引数を強制的に評価するため、

inline int DOUBLE(int a) {return a+a;}

a一度しか評価されない

#define DOUBLE(a) (a + a)

a2回評価します。この意味は

x = DOUBLE(someVeryLongFunction());

DOUBLE がマクロの場合、関数の場合よりも 2 倍の時間がかかります。

また、(意図的に) マクロ引数を括弧で囲むのを忘れたので、次のようにします。

DOUBLE(a << b)

まったく驚くべき結果をもたらします。DOUBLE マクロを次のように記述することを覚えておく必要があります。

#define DOUBLE(a) ((a) + (a))

言い換えれば、 自分の足で撃たれる可能性を最小限に抑えるためだけに、マクロを完全に記述する必要があります。間違えると、何年もその代償を払うことになります。

そうは言っても、マクロを使用するとコードが読みやすくなる場合がありますそれらはほとんどありませんが、存在します。それらの 1 つはassert、コードが再発明するマクロです。複雑なシステムでは、独自のカスタムの assertようなマクロを使用してローカル デバッグ スキームに結び付けることはかなり一般的です。これらはほとんどの場合__FILE____LINE__と条件のテキストを取得するためにマクロで実装されます。

しかし、それでも、これは典型的なassert実装方法です。

#ifdef NDEBUG
#   define assert(cond)
#else
#   define assert(cond) __assert(cond, __FILE__, __LINE__, #cond)
#endif

つまり、関数のようなマクロは関数呼び出しに展開されます。このように、 を呼び出すassertと、展開は見た目にかなり近く、引数の展開は期待どおりに行われます。

そして、他にもいくつかの用途があります。基本的に、ビルド プロセス自体からプログラムに情報を渡す必要があるときはいつでも、おそらくマクロ システムを経由する必要があります。それでも、マクロに関係するコードの量と、マクロが実行する量を最小限に抑える必要があります。

最後に 1 つ。コードが高速になると考えてマクロを使用したくなる場合は、これは悪魔の話であることに注意してください。昔は、小さな関数をマクロに変換すると、パフォーマンスが著しく向上する場合があったかもしれません。しかし、最近では:

  1. ほとんどのコンパイラはインライン関数をサポートしています。静的関数に対して自動的に実行するものもあります。

  2. 現代のコンピューターは非常に高速であるため、些細な関数を呼び出してもオーバーヘッドに気付かないことはほぼ間違いありません。

コンパイラがインライン関数を実行せず、それをより良いもの置き換えることができず、関数呼び出しのオーバーヘッドがボトルネックであることを証明した場合にのみ、いくつかのマクロを書くことを正当化できます。

多分。

于 2012-07-05T20:09:48.330 に答える
0

確かにマクロが必要な場合もありますが、マクロの数を最小限に抑えるようにしてください。あなたの例では、新しいマクロを作成する代わりに使用できる「assert」というマクロが既にあります。

C++ には、C でマクロを必要とするようなことをマクロなしで実行できる多くの機能があるため、マクロは C コードよりも C++ コードでは一般的ではありません。

于 2012-07-05T14:23:39.430 に答える
0

確かにマクロは関数を単純化し、読みやすくします。ただし、代わりにインライン関数の使用を検討する必要があります。

あなたの例では、EXIT_ON_FAILUREはインライン関数である可能性があります。マクロはコンパイラのエラーを不正確にするだけではありません (間違った場所にいくつかのエラーが表示される可能性があります)。マクロ、特に変数を使用する際に注意すべき点がいくつかあります。次の例を検討してください。

#define MY_MACRO(s) if (s * 2 >= 20) foo()

// later on your code:
MY_MACRO(5 + 5);

if (10 * 2 >= 20) foo()foo() が呼び出されることを期待できますが、そうはなりませんif (5 + 5 * 2 >= 20) foo()。したがって、マクロを定義するときは、常に変数の周りに () を使用することを覚えておく必要があります。

マクロはまた、プログラムのデバッグを難しくします。

于 2012-07-05T14:13:49.773 に答える