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