134

既定のパラメーターを使用するクラス コンストラクターを使用することは良い習慣ですか、それとも別のオーバーロードされたコンストラクターを使用する必要がありますか? 例えば:

// Use this...
class foo  
{
private:
    std::string name_;
    unsigned int age_;
public:
    foo(const std::string& name = "", const unsigned int age = 0) :
        name_(name),
        age_(age)
    {
        ...
    }
};

// Or this?
class foo  
{
private:
    std::string name_;
    unsigned int age_;
public:
    foo() :
    name_(""),
    age_(0)
{
}

foo(const std::string& name, const unsigned int age) :
        name_(name),
        age_(age)
    {
        ...
    }
};

どちらのバージョンでも動作するようです。例:

foo f1;
foo f2("Name", 30);

どのスタイルが好みまたは推奨されますか?その理由は?

4

12 に答える 12

96

間違いなくスタイルの問題です。パラメータが理にかなっている限り、デフォルトのパラメータを持つコンストラクタを好みます。標準のクラスもそれらを使用しており、これは彼らの好意を物語っています。

注意すべきことの 1 つは、1 つのパラメーターを除くすべてのパラメーターにデフォルトがある場合、クラスがそのパラメーターの型から暗黙的に変換される可能性があることです。詳細については、このスレッドを確認してください。

于 2008-10-09T14:59:28.587 に答える
43

特に C++ ではコンストラクターをチェーンできないため、デフォルトの引数を使用します (そのため、オーバーロードごとに初期化リストを複製する必要があり、場合によってはそれ以上を複製する必要があります)。

とはいえ、定数がインライン化される可能性がある (したがって、クラスのバイナリ インターフェイスの一部になる) など、既定の引数にはいくつかの落とし穴があります。注意すべきもう 1 つの点は、既定の引数を追加すると、明示的な複数引数のコンストラクターが暗黙的な 1 つの引数のコンストラクターに変わる可能性があることです。

class Vehicle {
public:
  Vehicle(int wheels, std::string name = "Mini");
};

Vehicle x = 5;  // this compiles just fine... did you really want it to?
于 2008-10-09T15:07:28.150 に答える
19

この説明は、コンストラクターだけでなく、メソッドと関数にも当てはまります。

デフォルトのパラメータを使用していますか?

良い点は、それぞれの場合にコンストラクター/メソッド/関数をオーバーロードする必要がないことです。

// Header
void doSomething(int i = 25) ;

// Source
void doSomething(int i)
{
   // Do something with i
}

悪い点は、ヘッダーでデフォルトを宣言する必要があるため、依存関係が隠されていることです。インライン関数のコードを変更する場合と同様に、ヘッダーのデフォルト値を変更する場合は、すべてのソースを再コンパイルする必要があります。このヘッダーを使用して、新しいデフォルトが使用されることを確認します。

そうしない場合でも、ソースは古いデフォルト値を使用します。

オーバーロードされたコンストラクター/メソッド/関数を使用していますか?

関数がインライン化されていない場合は、1つの関数の動作を選択して、ソースのデフォルト値を制御できるのは良いことです。例えば:

// Header
void doSomething() ;
void doSomething(int i) ;

// Source

void doSomething()
{
   doSomething(25) ;
}

void doSomething(int i)
{
   // Do something with i
}

問題は、複数のコンストラクター/メソッド/関数とそれらの転送を維持する必要があることです。

于 2008-10-09T16:14:37.843 に答える
13

私の経験では、デフォルトのパラメーターはその時点ではクールに見え、怠惰な要素を満足させますが、その後、クラスを使用していて、デフォルトが作動すると驚いています。だから、それは良い考えだとは思いません; className::className() を使用してから、className::init( arglist ) を使用することをお勧めします。その保守性のエッジのためだけに。

于 2008-10-09T15:04:50.650 に答える
7

サムの答えは、デフォルトの引数がオーバーロードよりもコンストラクターに適しているという理由を示しています。C ++-0xを追加すると、あるコンストラクターから別のコンストラクターへの委任が可能になり、デフォルトの必要がなくなります。

于 2008-10-09T17:01:27.467 に答える
5

どちらのアプローチでも機能します。ただし、オプションのパラメーターの長いリストがある場合は、デフォルトのコンストラクターを作成してから、set関数にこれへの参照を返させます。次に、セッターをチェーンします。

class Thingy2
{
public:
    enum Color{red,gree,blue};
    Thingy2();

    Thingy2 & color(Color);
    Color color()const;

    Thingy2 & length(double);
    double length()const;
    Thingy2 & width(double);
    double width()const;
    Thingy2 & height(double);
    double height()const;

    Thingy2 & rotationX(double);
    double rotationX()const;
    Thingy2 & rotatationY(double);
    double rotatationY()const;
    Thingy2 & rotationZ(double);
    double rotationZ()const;
}

main()
{
    // gets default rotations
    Thingy2 * foo=new Thingy2().color(ret)
        .length(1).width(4).height(9)
    // gets default color and sizes
    Thingy2 * bar=new Thingy2()
        .rotationX(0.0).rotationY(PI),rotationZ(0.5*PI);
    // everything specified.
    Thingy2 * thing=new Thingy2().color(ret)
        .length(1).width(4).height(9)
        .rotationX(0.0).rotationY(PI),rotationZ(0.5*PI);
}

これで、オブジェクトを作成するときに、オーバーライドするプロパティと設定したプロパティに明示的に名前を付けることができます。はるかに読みやすい:)

また、コンストラクターへの引数の順序を覚えておく必要がなくなりました。

于 2008-10-09T16:18:50.110 に答える
3

考慮すべきもう 1 つの点は、クラスを配列で使用できるかどうかです。

foo bar[400];

このシナリオでは、既定のパラメーターを使用するメリットはありません。

これは確かに機能しません:

foo bar("david", 34)[400]; // NOPE
于 2010-02-08T05:16:33.143 に答える
3

主に個人的な選択。ただし、オーバーロードは、既定のパラメーターで実行できることは何でも実行できますが、その逆はできません。

例:

オーバーロードを使用して A(int x, foo& a) および A(int x) を記述できますが、デフォルト パラメーターを使用して A(int x, foo& = null) を記述できません。

一般的なルールは、意味があり、コードを読みやすくするものは何でも使用することです。

于 2008-10-10T00:55:09.367 に答える
3

引数を使用してコンストラクターを作成するのが悪い場合 (多くの人が主張するように)、デフォルトの引数を使用してコンストラクターを作成するのはさらに悪いことです。私は最近、ctor のロジックはできるだけ最小限に抑える必要があるため、ctor の引数が悪いという意見に近づき始めました。誰かが意味をなさない引数を渡した場合、ctor でのエラー処理をどのように処理しますか? すべての呼び出し元が "新しい" 呼び出しを try ブロック内にラップする準備ができていない限り、例外をスローするか、"is-initialized" メンバー変数を設定することができます。これは一種の汚いハックです。

したがって、引数がオブジェクトの初期化ステージに渡されたことを確認する唯一の方法は、戻りコードを確認できる別の initialize() メソッドをセットアップすることです。

デフォルト引数の使用は、2 つの理由で良くありません。まず第一に、ctor に別の引数を追加したい場合は、最初にそれを配置して API 全体を変更する必要があります。さらに、ほとんどのプログラマーは、実際に使用される方法で API を理解することに慣れています。これは、正式なドキュメントが存在しない可能性がある組織内で使用される非公開 API に特に当てはまります。他のプログラマーは、大部分の呼び出しに引数が含まれていないことを確認すると、同じことを行い、デフォルトの引数が彼らに課すデフォルトの動作に幸いなことに気付かないままになります。

また、Google C++ スタイル ガイドでは、ctor 引数 (絶対に必要な場合を除く) と、関数またはメソッドへのデフォルト引数の両方を回避していることにも注意してください。

于 2008-10-09T15:57:55.397 に答える
2

この理由から、デフォルトのパラメーターを使用します。この例では、ctor パラメーターがメンバー変数に直接対応していると想定しています。しかし、そうではなく、オブジェクトを初期化する前にパラメーターを処理する必要がある場合はどうでしょう。共通の ctor を 1 つ持つことが最善の方法です。

于 2008-10-09T15:09:06.493 に答える
2

デフォルトのパラメータで私を悩ませていることの 1 つは、最後のパラメータを指定できず、最初のパラメータにはデフォルト値を使用できないことです。たとえば、あなたのコードでは、名前がなく特定の年齢で Foo を作成することはできません (ただし、私の記憶が正しければ、これは統一された構築構文を使用して C++0x で可能になります)。これは理にかなっている場合もありますが、非常に厄介な場合もあります。

私の意見では、経験則はありません。個人的には、最後の引数だけがデフォルト値を必要とする場合を除いて、複数のオーバーロードされたコンストラクター (またはメソッド) を使用する傾向があります。

于 2008-10-09T15:10:07.927 に答える
0

スタイルの問題ですが、Mattが言ったように、意図しない自動変換を回避するために、暗黙的な変換を「明示的」として許可するデフォルトの引数でコンストラクターをマークすることを確実に検討してください。これは必須ではありませんが(暗黙的に変換するラッパークラスを作成している場合は好ましくない場合があります)、エラーを防ぐことができます。

繰り返されるコードが嫌いなので、個人的にはデフォルトが好きです。YMMV。

于 2008-10-09T16:18:33.793 に答える