14

今日テストを受けましたが、質問の 1 つは C++ コンストラクターでの仮想メソッドの使用に関するものでした。私はこの質問に失敗し、問題はないと答えましたが、これを読んだ後、私は間違っていることがわかりました。

したがって、それを許可しない理由は、派生オブジェクトが完全に初期化されていないため、その仮想メソッドを呼び出すと無効な結果が生じる可能性があることを理解しています。

私の質問は Java/C# でどのように解決されましたか? 基本コンストラクターで派生メソッドを呼び出すことができることはわかっています。これらの言語にはまったく同じ問題があると思います。

4

6 に答える 6

15

Java のオブジェクト モデルは、C++ とは大きく異なります。Java では、クラス型のオブジェクトである変数を持つことはできません。代わりに、(クラス型の) オブジェクトへの参照のみを持つことができます。したがって、クラスのすべてのメンバー (参照のみ)nullは、派生オブジェクト全体がメモリ内に設定されるまで、簡単に開始されます。そうして初めて、コンストラクターが実行されます。したがって、基本コンストラクターが仮想関数を呼び出すまでに、その関数がオーバーライドされたとしても、オーバーライドされた関数は少なくとも派生クラスのメンバーを正しく参照できます。(これらのメンバー自体はまだ割り当てられていない可能性がありますが、少なくとも存在します。)

(それが役立つ場合は、Java のメンバーを持たないすべてのクラスfinalは、少なくとも原則として、技術的にデフォルトで構築可能であると考えることができます。C++ とは異なり、Java には定数や参照 ( C++ で初期化する必要があります) などはありません)。実際、初期化子リストはまったくありません. Java の変数は単に初期化する必要はありません. それらは 0 で始まるプリミティブか、 で始まるクラス型参照のいずれかですnull. 1 つの例外は非静的finalクラス メンバから発生します.これはリバウンドできず、すべてのコンストラクターのどこかに正確に1つの代入ステートメントを持つことによって実際に「初期化」する必要があります[これを指摘してくれた@josefxに感謝します!].)

于 2012-08-26T21:06:15.753 に答える
2

これを許可しない理由は、派生オブジェクトが完全に初期化されていないため、その仮想メソッドを呼び出すと無効な結果が生じる可能性があることを理解してください

間違い。C ++は、派生クラスではなく、メソッドの基本クラスの実装を呼び出します。「無効な結果」はありません。構成を回避する唯一の正当な理由は、動作がときどき驚きになることです。

これは、Javaが派生クラスの実装を呼び出すため、Javaとは異なります。

于 2012-08-27T03:21:55.837 に答える
1

C++ では、すべてのポリモーフィック クラス (少なくとも 1 つの仮想関数を持つクラス) の先頭に隠しポインター (通常は v-table などの名前) があり、仮想テーブル (を指す関数の配列) に初期化されます。そのクラスの各仮想関数の本体) であり、仮想関数 C++ を呼び出すときは を呼び出すだけな((v-table*)class)[index of your function]( function-parameters )ので、基本クラス コンストラクター v-table で仮想関数を呼び出す場合は、クラスが基本であり、それが基本クラスであるため、基本クラスの仮想テーブルを指します。子になるにはまだいくつかの初期化が必要であり、その結果、子からではなくベースから関数の実装を呼び出すことになり、これが純粋な仮想関数である場合、アクセス違反が発生します。
しかし、Javaではこれはこのようなものではなく、Java全体ではクラス全体がstd::map<std::string, JValue>この場合のようなものですJValueベースのコンストラクboost::variantターで関数を呼び出すと、マップ内の関数名が検索されて呼び出されますが、それはまだ子からの値ではありませんが、それでも呼び出すことができます。コンストラクターの前にプロトタイプが作成されたためprototype、子から関数を正常に呼び出すことができますが、関数が子のコンストラクターからの初期化を必要とする場合でも、エラーまたは無効な結果が得られます。
したがって、一般に、基本クラスで子から関数 (たとえば、仮想関数) を呼び出すことはお勧めできません。クラスでこれを行う必要がある場合は、初期化メソッドを追加して、子クラスのコンストラクターから呼び出します。

于 2012-08-26T21:17:18.910 に答える
1

すべての Java コンストラクターは次のようになります。

class Foo extends Bar {
  Foo() {
    super(); // creates Bar
    // do things
  }
}

したがって、派生メソッドで動作するコードを に配置するdo thingsと、 でコンストラクターを呼び出した後、この基本オブジェクトが適切に初期化されたというロジックのようです。super();

于 2012-08-26T21:06:42.180 に答える
0

Java は、この問題を完全に回避するわけではありません。

サブクラスのフィールドに依存するスーパークラス コンストラクターから呼び出されるオーバーライドされたメソッドは、それらのフィールドが初期化される前に呼び出されます。

クラス階層全体を管理している場合は、もちろん、オーバーライドがサブクラス フィールドに依存しないようにすることもできます。ただし、コンストラクターから仮想メソッドを呼び出さない方が安全です。

于 2012-08-26T22:44:07.817 に答える
0

Java/C# は、C++ で基本クラスから前方に構築するのではなく、派生クラスから後方に構築することで、この問題を回避していると思います。

Java はクラス コンストラクターで暗黙的に super() を呼び出すため、派生クラス コンストラクターで記述されたコードの最初の行が呼び出されるまでに、継承されたすべてのクラスのすべてのコンストラクターが呼び出されたことが保証され、新しいインスタンスが完全に初期化されます。 .

C++でも、クラスの新しいインスタンスは基本クラスとして始まり、継承チェーンを下るにつれて最終的なクラスタイプに「アップグレード」されると思います。これは、コンストラクターで仮想関数を呼び出すと、実際には基底クラスのその関数のバージョンを呼び出すことになることを意味します。

Java およびおそらく C# では、新しいインスタンスが必要なクラス型として開始されるため、正しいバージョンの仮想メソッドが呼び出されます。

于 2012-08-26T21:16:05.600 に答える