#defineを使用してコーディングを簡略化するためのコードの完全な行を定義することは、良いプログラミングか悪いプログラミングかという見方はありますか?たとえば、単語の束を一緒に印刷する必要がある場合、入力が煩わしくなります。
<< " " <<
Coutステートメントの単語間にスペースを挿入します。ただできる
#define pSpace << " " <<
そしてタイプ
cout << Word1 pSpace Word2 << endl;
私にとって、これはコードの明確さを増したり減じたりせず、タイピングを少し簡単にします。通常はデバッグのために、タイピングがはるかに簡単になると考えることができる他のケースがあります。
これについて何か考えはありますか?
編集:すべての素晴らしい答えをありがとう!この質問は、多くの繰り返し入力を行った直後に思い付きましたが、他に使用するのに混乱の少ないマクロがあるとは思いもしませんでした。すべての回答を読みたくない場合、最善の代替策は、IDEのマクロを使用して、繰り返し入力することを減らすことです。
コードを書くのは簡単です。コードを読むのは難しいです。
コードを1回記述します。それは何年も生き、人々はそれを100回読みます。
書き込みではなく読み取り用にコードを最適化します。
個人的には嫌いです。私がこの手法を思いとどまらせる理由はいくつかあります。
コンパイル時に、実際のコードの変更が重要になる場合があります。次の人がやって来て、彼の#defineまたは関数呼び出しに閉じ括弧を含めます。コードの特定のポイントで書かれていることは、前処理後に何ができるのかとはかけ離れています。
読めません。それは今のところはっきりしているかもしれませんが、それがこの1つの定義だけの場合です。それが習慣になると、すぐに何十もの#definesができてしまい、自分自身を見失ってしまいます。しかし、最悪の場合、他の誰も何を理解することができませんWord1 pSpace Word2
は、正確に(#defineを検索せずに)意味します。
外部ツールの問題になるかもしれません。どういうわけか、閉じ括弧が含まれているが開始括弧が含まれていない#defineで終わるとします。すべてうまくいくかもしれませんが、編集者やその他のツールはfunction(withSomeCoolDefine;
かなり奇妙なものです(つまり、エラーなどを報告します)。 (同様の例:定義内の関数呼び出し-分析ツールはこの呼び出しを見つけることができますか?)
メンテナンスが非常に難しくなります。メンテナンスによってもたらされる通常の問題に加えて、これらすべてを定義します。上記の点に加えて、リファクタリングのツールサポートも悪影響を受ける可能性があります。
これについての私の主な考えは、コードを書くときは原則として「タイピングを容易にする」ことは決して使用しないということです。
コードを書くときの私の主なルールは、コードを読みやすくすることです。これの背後にある理論的根拠は、単にコードが書かれているよりも桁違いに多く読み取られるということです。そのため、あなたがloseを慎重に、整然と、正しくレイアウトして書く時間は、実際にさらに読んだり理解したりするのに費やされます。
そのため、使用する#defineは、通常の代替<<
およびother-stuff。それは最小の驚きのルールを破り、私見は良いことではありません。
この質問は、マクロをどのようにうまく使用できないかを示す明確な例です。他の例を見る(そして楽しませる)には、 この質問 を参照してください。
そうは言っても、私はマクロをうまく組み込んだと私が考えるものの実例を挙げます。
最初の例は、ユニットテストフレームワークである CppUnit にあります。他の標準的なテストフレームワークと同様に、テストクラスを作成し、テストの一部として実行するメソッドを何らかの方法で指定する必要があります。
#include <cppunit/extensions/HelperMacros.h>
class ComplexNumberTest : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE( ComplexNumberTest );
CPPUNIT_TEST( testEquality );
CPPUNIT_TEST( testAddition );
CPPUNIT_TEST_SUITE_END();
private:
Complex *m_10_1, *m_1_1, *m_11_2;
public:
void setUp();
void tearDown();
void testEquality();
void testAddition();
}
ご覧のとおり、クラスには最初の要素としてマクロのブロックがあります。新しいメソッドtestSubtraction
を追加した場合、テスト実行に含めるために何をする必要があるかは明らかです。
これらのマクロブロックは、次のように展開されます。
public:
static CppUnit::Test *suite()
{
CppUnit::TestSuite *suiteOfTests = new CppUnit::TestSuite( "ComplexNumberTest" );
suiteOfTests->addTest( new CppUnit::TestCaller<ComplexNumberTest>(
"testEquality",
&ComplexNumberTest::testEquality ) );
suiteOfTests->addTest( new CppUnit::TestCaller<ComplexNumberTest>(
"testAddition",
&ComplexNumberTest::testAddition ) );
return suiteOfTests;
}
どちらを読んで維持したいですか?
別の例は、関数をメッセージにマップするMicrosoft MFCフレームワークです。
BEGIN_MESSAGE_MAP( CMyWnd, CMyParentWndClass )
ON_MESSAGE( WM_MYMESSAGE, OnMyMessage )
ON_COMMAND_RANGE(ID_FILE_MENUITEM1, ID_FILE_MENUITEM3, OnFileMenuItems)
// ... Possibly more entries to handle additional messages
END_MESSAGE_MAP( )
では、「グッドマクロ」と恐ろしい悪の種類を区別するものは何でしょうか。
他の方法では単純化できないタスクを実行します。テンプレートメソッドを使用して同じことを達成できるため、2つの要素間の最大値を決定するマクロを記述するのは間違っています。ただし、C++言語ではエレガントに処理できない複雑なタスク(メッセージコードをメンバー関数にマッピングするなど)があります。
彼らは非常に厳格な、正式な使用法を持っています。これらの例の両方で、マクロブロックは開始マクロと終了マクロによって通知され、中間のマクロはこれらのブロック内にのみ表示されます。通常のC++があり、マクロのブロックで少し言い訳をしてから、再び通常に戻ります。 「邪悪なマクロ」の例では、マクロはコード全体に散在しており、不幸な読者はC++ルールがいつ適用され、いつ適用されないかを知る方法がありません。
何度も何度も入力し直すのが面倒だと感じたコードのスニペットを挿入するために、お気に入りのIDE /テキストエディターを調整すると、より良い方法になります。そして、比較のための「丁寧な」用語の方が良いです。実は、前処理がエディターのマクロに勝っているとき、私は似たようなケースについて考えることができません。まあ、1つかもしれません-いくつかの不可解で不幸な理由により、コーディングにさまざまなツールセットを常に使用している場合。しかし、それは正当化ではありません:)
また、テキストの前処理が非常に読みにくく複雑になる場合(パラメーター化された入力について考えてみてください)は、より複雑なシナリオに適したソリューションになる可能性があります。
他の人はあなたがそれをしてはいけない理由をすでに説明しています。あなたの例は明らかにマクロで実装されるに値しません。ただし、読みやすくするためにマクロを使用する場合には、さまざまなケースがあります。
そのようなテクニックの賢明なアプリケーションの悪名高い例は Clang プロジェクトです:.def
ファイルがそこでどのように使用されるかを参照してください。マクロと#include
を使用すると、型宣言、case
ステートメント、デフォルトの初期化子などに展開される、類似のもののコレクションに対して単一の、時には完全に宣言的な定義を提供できます。これにより、保守性が向上します。重要なのは、たとえば、新しいcase
を追加したときに、どこにでも新しいenum
ステートメントを追加することを忘れないことです。
したがって、他の強力なツールと同様に、Cプリプロセッサを慎重に使用する必要があります。 「これを決して使用してはならない」や「常に使用しなければならない」など、プログラミングの技術には一般的な規則はありません。すべてのルールはガイドラインにすぎません。
そのような#definesを使用することは決して適切ではありません。あなたの場合、これを行うことができます:
class MyCout
{
public:
MyCout (ostream &out) : m_out (out), m_space_pending (false)
{
}
template <class T>
MyCout &operator << (T &value)
{
if (m_space_pending)
m_out << " ";
m_out << value;
m_space_pending = false;
return *this;
}
MyCout &operator << (const char *value)
{
if (m_space_pending)
m_out << " ";
m_out << value;
m_space_pending = true;
return *this;
}
MyCout &operator << (char *value) { return operator << (static_cast <const char *> (value)); }
MyCout &operator << (ostream& (*fn)(ostream&)) { m_out << fn; return *this; }
private:
ostream
&m_out;
bool
m_space_pending;
};
int main (int argc, char *argv [])
{
MyCout
space_separated (cout);
space_separated << "Hello" << "World" << endl;
}
番号。
コードで使用することを目的としたマクロの場合、適切性をテストするための適切なガイドラインは、展開を括弧(式の場合)または中括弧(コードの場合)で囲み、コンパイルできるかどうかを確認することです。
// These don't compile:
#define pSpace (<< " " <<)
cout << Word1 pSpace Word2 << endl;
#define space(x) (" " << (x))
cout << Word1 << space(Word2) << endl;
// These do:
#define FOO_FACTOR (38)
x = y * FOO_FACTOR;
#define foo() (cout << "Foo" << endl)
foo();
#define die(c) { if ((c)) { exit(1); } }
die(foo > 8);
#define space(x) (" " + string((x)))
cout << "foo" << space("bar") << endl;
宣言で使用されるマクロ(Andrew Shepherdの回答の例のように)は、周囲のコンテキストを混乱させない限り(たとえば、public
とprivate
の切り替え)、より緩やかなルールセットで回避できます。 )。
純粋な「C」プログラムで行うのは妥当なことです。
C++プログラムでは不要であり、混乱を招きます。
C++でコードの繰り返し型付けを回避する方法はたくさんあります。 IDEによって提供される機能を使用することから(viを使用しても "%s/ pspace /<< " " <</g
"を使用すると、入力を最小限に抑えながら、標準の読み取り可能なコードを生成できます。プライベートメソッドを定義してこれを実装することも、より複雑なケースではC++テンプレートの方が簡潔で簡単です。
C++では、これは演算子のオーバーロードで解決できます。または、可変個関数のような単純なものでもかまいません。
lineWithSpaces(Word1, Word2, Word3, ..., wordn)
は単純で、pSpaces
を何度も入力する手間を省きます。
したがって、あなたの場合は大したことではないように見えるかもしれませんが、よりシンプルで堅牢なソリューションがあります。
一般に、難読化を導入せずにマクロの使用が大幅に短くなるケースはほとんどなく、実際の言語機能を使用した十分に短い解決策があります(マクロは単なる文字列置換にすぎません)。
#defineを使用してコーディングを簡略化するためのコードの完全な行を定義することは、良いプログラミングか悪いプログラミングかという見方はありますか?
はい、とても悪いです。私はこれをしている人々を見たことさえあります:
#define R return
タイピング(達成しようとしていること)を節約するため。
そのようなコードは this のような場所にのみ属します。