web-dev-qa-db-ja.com

#ifdefを使用して、開発中にさまざまなタイプの動作を切り替える

開発中に#ifdefを使用して異なるタイプの動作を切り替えることは良い習慣ですか?たとえば、既存のコードの動作を変更したい場合、動作を変更する方法がいくつかあります。異なる実装を切り替えて、異なるアプローチをテストして比較する必要があります。通常、コードの変更は複雑で、異なるファイルの異なるメソッドに影響を与えます。

私は通常いくつかの識別子を導入し、そのようなことをします

void foo()
{
    doSomething1();
#ifdef APPROACH1
    foo_approach1();
#endif
    doSomething2();
#ifdef APPROACH2
    foo_approach2();
#endif
}

void bar()
{
    doSomething3();
#ifndef APPROACH3
    doSomething4();
#endif
    doSomething5();
#ifdef APPROACH2
    bar_approach2();
#endif
}

int main()
{
    foo();
    bar();
    return 0;
}

これにより、さまざまなアプローチをすばやく切り替えて、ソースコードの1つのコピーだけですべてを実行できます。それは開発のための良いアプローチですか、それともより良い実践がありますか?

28
Yury Shkliaryk

この使用例では、バージョン管理ブランチを使用することをお勧めします。これにより、実装を比較し、それぞれに個別の履歴を維持できます。決定を行い、いずれかのバージョンを削除する必要がある場合は、エラーが発生しやすい編集を行う代わりに、そのブランチを破棄するだけです。

9
Karl Bielefeldt

ハンマーを持っていると、すべてが釘のように見えます。方法を知ったら魅力的です#ifdefは、プログラムでカスタム動作を取得するための一種の手段として使用します。私も同じ間違いをしたので知っています。

MFC C++で記述されたレガシープログラムを継承しましたが、すでに#ifdefプラットフォーム固有の値を定義します。これは、特定のマクロ値を定義する(または場合によっては定義しない)だけで、32ビットプラットフォームまたは64ビットプラットフォームで使用できるようにプログラムをコンパイルできることを意味しました。

その後、クライアント用のカスタム動作を作成する必要があるという問題が発生しました。ブランチを作成してクライアント用に別のコードベースを作成することもできましたが、それではメンテナンスが大変になります。起動時にプログラムによって読み取られる構成値を定義し、これらの値を使用して動作を決定することもできますが、その後、カスタムセットアップを作成して、各クライアントの構成ファイルに適切な構成値を追加する必要があります。

私は誘惑されて、私は降参しました。私は#ifdefさまざまな動作を区別するためのコード内のセクション。間違いなく、最初は何も上にありませんでした。非常にマイナーな動作の変更が行われたため、プログラムのバージョンをクライアントに再配布でき、複数のバージョンのコードベースを用意する必要はありません。

時間が経つにつれ、これは保守の地獄になりましたとにかくプログラムが全体で一貫して動作しなくなったためです。プログラムのバージョンをテストする場合、クライアントが誰であるかを必ず知っていなければなりませんでした。コードは、1つまたは2つのヘッダーファイルに削減しようとしましたが、非常に雑然としていて、#ifdef提供されたということは、そのような解決策が悪性癌のようにプログラム全体に広がることを意味しました。

私はそれから私のレッスンを学びました、そしてあなたもそうするべきです。どうしても必要な場合に使用し、プラットフォームの変更にのみ使用してください。プログラム(したがってクライアント)間の動作の違いに取り組む最善の方法は、起動時にロードされた構成のみを変更することです。プログラムは一貫性が保たれ、デバッグだけでなく読みやすくなります。

42
Neil

一時的に(たとえば、チェックインの前に)何をしているのも問題ありません:テクニックのさまざまな組み合わせをテストしたり、コードのセクションを無視したりするのに最適な方法です(ただし、それ自体の問題)。

しかし警告の言葉:#ifdefブランチを保持しないでください同じことを読んで時間を無駄にすることよりもイライラすることは4つあります。 私が読んでいる方

#ifdefを読み飛ばすには、実際に読み飛ばすことを覚えておく必要があるため、労力がかかります!絶対に必要以上に難しくしないでください。

#ifdefsはできるだけ使用しないでください。開発環境内でこれを行うには、デバッグ/リリースビルドなどのpermanentの違い、または異なるアーキテクチャーに対して一般的に行う方法があります。

#ifdef分割が必要な、含まれているライブラリバージョンに依存するライブラリ機能を記述しました。時々それが唯一の方法、または最も簡単な方法かもしれませんが、それでもそれらを維持することに腹を立てる必要があります。

21
Erdrik Ironrose

このような#ifdefsを使用すると、コードが非常に読みにくくなります。

したがって、いいえ、そのような#ifdefsを使用しないでください。

Ifdefsを使わない理由はたくさんあるかもしれませんが、私にはこれで十分です。

void foo()
{
    doSomething1();
#ifdef APPROACH1
    foo_approach1();
#endif
    doSomething2();
#ifdef APPROACH2
    foo_approach2();
#endif
}

それができる多くのことをすることができます:

void foo()
{
    doSomething1();
    doSomething2();
}

void foo()
{
    doSomething1();
    foo_approach1();
    doSomething2();
}

void foo()
{
    doSomething1();
    doSomething2();
    foo_approach2();
}

void foo()
{
    doSomething1();
    foo_approach1();
    doSomething2();
    foo_approach2();
}

どのアプローチが定義されているかによって異なります。それが何をするかは、最初の見た目では絶対に明確ではありません。

1
Pieter B