web-dev-qa-db-ja.com

C ++-クラス内のオブジェクトの構築

私はC++にかなり慣れていないので、これについてはわかりません。現在の問題をまとめた次の例を見てください。

class Foo
{
    //stuff
};

class Bar
{
    Foo foo;
};

したがって、Barには、参照やポインタだけでなく、完全なFooオブジェクトが含まれています。このオブジェクトはデフォルトのコンストラクターによって初期化されていますか?コンストラクタを明示的に呼び出す必要がありますか?呼び出す場合、方法と場所は?

ありがとう。

23
SolarBear

デフォルトのコンストラクタによって初期化されます。別のコンストラクターを使用する場合は、次のようなものがあります。

class Foo
{
    public: 
    Foo(int val) { }
    //stuff
};

class Bar
{
    public:
    Bar() : foo(2) { }

    Foo foo;
};
23

C++では、構築はかなり難しいトピックです。簡単な答えは状況によって異なりますです。 Fooが初期化されるかどうかは、Foo自体の定義に依存します。 2番目の質問について:BarでFooを初期化する方法:初期化リストが答えです。

一般的なコンセンサスは、Fooが暗黙のデフォルトコンストラクター(コンパイラー生成)によってデフォルトで初期化されることですが、trueである必要はありません。

Fooにユーザー定義のデフォルトコンストラクタがない場合、Fooは初期化されません。より正確に言うとユーザー定義のデフォルトコンストラクターがないBarまたはFooの各メンバーは、コンパイラーが生成したBarのデフォルトコンストラクターによって初期化されません

class Foo {
   int x;
public:
   void dump() { std::cout << x << std::endl; }
   void set() { x = 5; }
};
class Bar {
   Foo x;
public:
   void dump() { x.dump(); }
   void set() { x.set(); } 
};
class Bar2
{
   Foo x;
public:
   Bar2() : Foo() {}
   void dump() { x.dump(); }
   void set() { x.set(); }
};
template <typename T>
void test_internal() {
   T x;
   x.dump();
   x.set();
   x.dump();
}
template <typename T>
void test() {
   test_internal<T>();
   test_internal<T>();
}
int main()
{
   test<Foo>(); // prints ??, 5, 5, 5, where ?? is a random number, possibly 0
   test<Bar>(); // prints ??, 5, 5, 5
   test<Bar2>(); // prints 0, 5, 0, 5
}

これで、Fooにユーザー定義のコンストラクターがある場合、Barにユーザー初期化コンストラクターがあるかどうかに関係なく、Fooは常に初期化されます。 Barに、Fooの(おそらく暗黙的に定義された)コンストラクターを明示的に呼び出すユーザー定義のコンストラクターがある場合、Fooは実際に初期化されます。 Barの初期化リストがFooコンストラクターを呼び出さない場合は、Barにユーザー定義コンストラクターがなかった場合と同じになります。

テストコードはいくつかの説明が必要な場合があります。ユーザーコードが実際にコンストラクターを呼び出さなくても、コンパイラーが変数を初期化するかどうかに関心があります。オブジェクトが初期化されているかどうかをテストします。関数でオブジェクトを作成するだけの場合、変更されておらず、すでにゼロが含まれているメモリ位置に到達する可能性があります。運と成功を区別したいので、関数で変数を定義し、関数を2回呼び出します。最初の実行では、メモリの内容を出力して強制的に変更します。関数の2回目の呼び出しでは、スタックトレースが同じであるため、変数はまったく同じメモリ位置に保持されます。初期化された場合は0に設定されます。それ以外の場合は、古い変数とまったく同じ位置に古い変数が保持されます。

各テスト実行で、最初に出力される値は、初期化された値(実際に初期化されていた場合)またはそのメモリ位置の値であり、場合によっては0になります。2番目の値は、値を表す単なるテストトークンです手動で変更した後のメモリ位置。 3番目の値は、関数の2回目の実行から取得されます。変数が初期化されている場合、0にフォールバックします。オブジェクトが初期化されていない場合、そのメモリは古い内容を保持します。

C++コンパイラーがクラスごとに生成できる関数は4つあります(提供しない場合)。デフォルトのコンストラクター、コピーコンストラクター、代入演算子、デストラクタです。 C++標準(第12章「特殊関数」)では、これらは「暗黙的に宣言された」および「暗黙的に定義された」と呼ばれます。彼らは一般公開されます。

コンストラクターで「暗黙的に定義された」と「デフォルト」を混同しないでください。デフォルトのコンストラクターは、引数がある場合、引数なしで呼び出すことができるコンストラクターです。コンストラクタを指定しない場合、デフォルトのコンストラクタが暗黙的に定義されます。各基本クラスとデータメンバーの既定のコンストラクターを使用します。

つまり、Fooクラスには暗黙的に定義されたデフォルトコンストラクターがあり、Bar(ユーザー定義コンストラクターがないようです)は、Fooのデフォルトコンストラクターを呼び出す暗黙的に定義されたデフォルトコンストラクターを使用しています。

Barのコンストラクターを作成したい場合は、初期化リストでfooに言及できますが、デフォルトのコンストラクターを使用しているため、実際に指定する必要はありません。

Fooのコンストラクターを作成する場合、コンパイラーはデフォルトのコンストラクターを自動的に生成しないので、必要な場合はコンストラクターを指定する必要があることに注意してください。したがって、Foo(int n);のようなものをFooの定義に入れて、デフォルトのコンストラクター(Foo();またはFoo(int n = 0);)を明示的に記述しなかった場合、 Fooのデフォルトコンストラクターを使用できなかったため、現在の形式のバーはありませんでした。この場合、Bar(int n = 0): foo(n) {}のようなコンストラクターでBarコンストラクターがFooを初期化する必要があります。 (Barコンストラクタが最初にfooを初期化しようとすると失敗するため、Bar(int n = 0) {foo = n;}などは機能しないことに注意してください。)

6
David Thornley

Barのコンストラクター内でfooのコンストラクターを明示的に呼び出さない場合、デフォルトのコンストラクターが使用されます。コンストラクタを明示的に呼び出すことでこれを制御できます

Bar::Bar() : foo(42) {}

もちろん、これはコードにFoo :: Foo(int)を追加することを前提としています:)

3
JaredPar

オブジェクトの初期状態を設定するためのコンストラクタ。継承階層の場合、基本クラスオブジェクトは、継承階層(OO用語のIS-A関係)の後に派生クラスオブジェクトが続く順序で構築されます。

同様に、オブジェクトが別のオブジェクトに埋め込まれている場合(OO用語または包含関係のHAS-A関係)は、埋め込まれたオブジェクトのコンストラクターが宣言順に呼び出されます。

コンパイラーはクラスBのすべてのメンバーを解析してから、各メソッドのコードを生成します。その時点で、すべてのメンバーとその順序がわかっているため、特に指定されていない限り、デフォルトのコンストラクターを使用してメンバーを順番に作成します。したがって、fooはデフォルトのコンストラクタを明示的に呼び出さずに作成されます。あなたがする必要があるのは、Barのオブジェクトを作成することだけです。

1
user6882413

したがって、Barには、参照やポインタだけでなく、完全なFooオブジェクトが含まれています。このオブジェクトはデフォルトのコンストラクターによって初期化されていますか?

Fooにデフォルトのctorがある場合、Fooタイプのオブジェクトは、Barタイプのオブジェクトを作成するときにデフォルトのctorを使用します。それ以外の場合は、Foo ctorを自分で呼び出す必要があります。そうしないと、Barのc​​torがコンパイラーに不平を言うようになります。

例えば:

class Foo {
public:
 Foo(double x) {}
};

class Bar  {
 Foo x;
};

int main() {
 Bar b;
}

上記はコンパイラに次のような文句を言うでしょう:

「コンストラクター 'Bar :: Bar()'で:5行目:エラー: 'Foo :: Foo()'の呼び出しに一致する関数がありません

コンストラクタを明示的に呼び出す必要がありますか?呼び出す場合、方法と場所は?

上記の例を次のように変更します。

class Foo {
 public:
  Foo(double x) {} // non-trivial ctor
};

class Bar  {     
 Foo x;
public:
  Bar() : x(42.0) {} // non-default ctor, so public access specifier required
};

int main() {
 Bar b;
}
1
dirkgently

完全なオブジェクト。いいえ、それはBarのデフォルトのコンストラクタで構築されたデフォルトです。

ここで、Fooに、たとえばintのみをとるコンストラクターがあったとします。 Fooのコンストラクターを呼び出すには、Barにコンストラクターが必要です。

class Foo {
public:
    Foo(int x) { .... }
};

class Bar {
public:
    Bar() : foo(42) {}

    Foo foo;
};

ただし、FooにデフォルトのコンストラクターFoo()があった場合、コンパイラーはBarのコンストラクターを自動的に生成し、Fooのデフォルトを呼び出します(つまり、Foo())

1
Macke

特に指定しない限り、fooはデフォルトのコンストラクタを使用して初期化されます。他のコンストラクタを使用する場合は、Barの初期化リストで使用する必要があります。

Bar::Bar( int baz ) : foo( baz )
{
    // Rest of the code for Bar::Bar( int ) goes here...
}
1
Naaff

C++でデフォルトのコンストラクターを明示的に呼び出す必要はありません。呼び出されます。別のコンストラクタを呼び出したい場合は、次のようにします。

Foo foo(somearg)
0
Shane C. Mason