デフォルトのパラメーターを使用するクラスコンストラクターを使用することをお勧めしますか、それとも別のオーバーロードされたコンストラクターを使用する必要がありますか?例えば:
// 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);
どのスタイルを好みますか、またはお勧めしますか?その理由は?
間違いなくスタイルの問題。パラメーターが意味をなす限り、デフォルトのパラメーターを持つコンストラクターを好みます。標準のクラスもそれらを使用します。
気を付けなければならないことの1つは、1つのパラメーターを除くすべてのパラメーターにデフォルトがある場合、クラスはそのパラメータータイプから暗黙的に変換できることです。詳細については、 このスレッド をご覧ください。
特に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?
この説明は、コンストラクターだけでなく、メソッドと関数にも適用されます。
良いことは、各ケースでコンストラクター/メソッド/関数をオーバーロードする必要がないことです:
// 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
}
問題は、複数のコンストラクター/メソッド/関数、およびそれらの転送を維持する必要があることです。
私の経験では、デフォルトのパラメーターはその時はクールに見え、私の怠factorの要因を幸せにしますが、その後、クラスを使用しているので、デフォルトが作動すると驚いています。 ; className :: className()を使用してからclassName :: init(arglist)を使用する方が適切です。その保守性のためだけに。
どちらのアプローチも機能します。ただし、オプションのパラメーターの長いリストがある場合は、デフォルトのコンストラクターを作成し、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);
}
オブジェクトを作成するときに、オーバーライドするプロパティと、設定したプロパティに明示的に名前を付けるものを選択できます。より読みやすい:)
また、コンストラクターへの引数の順序を覚える必要がなくなりました。
引数を持つコンストラクタを作成するのが悪い場合(多くの人が主張するように)、デフォルト引数でコンストラクタを作成するのはさらに悪いです。あなたのctorロジックは可能な限り最小限である必要があるため俳優のエラー処理をどのように処理しますか、誰かが意味をなさない引数を渡す必要がありますか?すべての呼び出し元がtryブロック内に「新しい」呼び出しをラップする準備ができていない限り、悪いニュースである例外をスローするか、「初期化された」メンバー変数を設定します。
したがって、オブジェクトの初期化段階に渡される引数を確実にする唯一の方法は、戻りコードを確認できる別のinitialize()メソッドを設定することです。
デフォルト引数の使用は、次の2つの理由で不適切です。まず第一に、別の引数をctorに追加したい場合は、最初に引数を入れてAPI全体を変更する必要があります。さらに、ほとんどのプログラマーは、実際に使用される方法でAPIを理解することに慣れています-これは、特に正式なドキュメントが組織内で使用される非パブリックAPIに当てはまります存在しない可能性があります。他のプログラマが呼び出しの大部分に引数が含まれていないことに気付いた場合、彼らは同じことを行いますが、デフォルト引数がそれらに課すデフォルトの振る舞いを至福に気づかないままにします。
また、 google C++スタイルガイド が両方のctor引数(絶対に必要でない限り)と 関数またはメソッドのデフォルト引数 を排除していることに注意する価値があります。
考慮すべきもう1つのことは、クラスを配列で使用できるかどうかです。
foo bar[400];
このシナリオでは、デフォルトのパラメーターを使用する利点はありません。
これは確かに機能しません:
foo bar("david", 34)[400]; // NOPE
このため、デフォルトのパラメーターを使用します。この例では、ctorパラメーターがメンバー変数に直接対応していることを前提としています。しかし、そうでない場合、オブジェクトを初期化する前にパラメーターを処理する必要がある場合はどうでしょう。共通の俳優が1人いるのが最善の方法です。
主に個人的な選択。ただし、オーバーロードはデフォルトのパラメータでできることは何でもできますが、notはその逆です。
例:
オーバーロードを使用してA(int x、foo&a)およびA(int x)を記述できますが、A(int x、foo&= null)を記述するためにデフォルトのパラメーターを使用することはできません。
一般的なルールは、意味のあるものを使用し、コードを読みやすくすることです。
デフォルトのパラメーターに悩まされることの1つは、最後のパラメーターを指定することはできず、最初のパラメーターにデフォルト値を使用することです。たとえば、コードでは、名前なしで特定の年齢のFooを作成することはできません(ただし、私が正しく覚えていれば、これはC++ 0xで統一された構文で可能になります)。これは理にかなっている場合もありますが、本当に厄介な場合もあります。
私の意見では、経験則はありません。個人的には、複数のオーバーロードされたコンストラクター(またはメソッド)を使用する傾向がありますが、最後の引数のみにデフォルト値が必要な場合を除きます。
スタイルの問題ですが、マットが言ったように、意図しない自動変換を避けるために暗黙的な変換を「明示的」として許可するデフォルト引数でコンストラクタをマークすることを必ず検討してください。これは要件ではありません(暗黙的に変換するラッパークラスを作成する場合は望ましくない場合があります)が、エラーを防ぐことができます。
私は繰り返しコードが嫌いなので、適切な場合はデフォルトを好みます。 YMMV。