18

Java の場合:

class Base {
    public Base() { System.out.println("Base::Base()"); virt(); }
    void virt()   { System.out.println("Base::virt()"); }
}

class Derived extends Base {
    public Derived() { System.out.println("Derived::Derived()"); virt(); }
    void virt()      { System.out.println("Derived::virt()"); }
}

public class Main {
    public static void main(String[] args) {
        new Derived();
    }
}

これは出力されます

Base::Base()
Derived::virt()
Derived::Derived()
Derived::virt()

ただし、C++ では結果が異なります。

Base::Base()
Base::virt() // ← Not Derived::virt()
Derived::Derived()
Derived::virt()

( C++ コードについては、 http://www.parashift.com/c++-faq-lite/calling-virtuals-from-ctors.htmlを参照してください)

Java と C++ のこのような違いの原因は何ですか? vtableが初期化された時ですか?

編集: Java と C++ のメカニズムは理解しています。私が知りたいのは、この設計上の決定の背後にある洞察です。

4

7 に答える 7

15

どちらのアプローチにも明らかに欠点があります。

  • thisJavaでは、メンバーがまだ初期化されていないために正しく使用できないメソッドが呼び出されます。
  • C ++では、C ++がクラスを構築する方法がわからない場合、直感的でないメソッド(つまり、派生クラスのメソッドではない)が呼び出されます。

各言語がそれが行うことを行う理由は未解決の質問ですが、どちらもおそらく「より安全な」オプションであると主張しています。C++の方法は、初期化されていないメンバーの使用を防ぎます。Javaのアプローチでは、クラスのコンストラクター内で(ある程度)多態的なセマンティクスが可能になります(これは完全に有効なユースケースです)。

于 2012-11-18T13:24:35.390 に答える
13

さて、あなたはすでにFAQの議論にリンクしていますが、それは主に問題指向であり、理論的根拠には触れていません。その理由です。

要するに、それは型安全性のためです。

これは、型の安全性に関してC ++がJavaやC#に勝る数少ないケースの1つです。;-)

クラスを作成するときA、C ++では、各コンストラクターに新しいインスタンスを初期化させて、クラス不変Aと呼ばれるその状態に関するすべての一般的な仮定が成り立つようにすることができます。たとえば、クラス不変条件の一部は、ポインタメンバーが動的に割り当てられたメモリを指している場合があります。公開されている各メソッドがクラス不変条件を保持している場合、各メソッドへのエントリも保持されることが保証されます。これにより、少なくとも適切に選択されたクラス不変条件の場合、作業が大幅に簡素化されます。

その後、各メソッドでそれ以上のチェックは必要ありません。

対照的に、MicrosoftのMFCおよびATLライブラリなどの2フェーズ初期化を使用すると、メソッド(非静的メンバー関数)が呼び出されたときにすべてが適切に初期化されているかどうかを完全に確認することはできません。これはJavaやC#と非常に似ていますが、これらの言語ではクラス不変の保証がないのは、クラス不変の概念を有効にするだけで積極的にサポートしないという点が異なります。つまり、基本クラスのコンストラクターから呼び出されたJavaおよびC#の仮想メソッドは、(派生)クラスの不変条件がまだ確立されていない、まだ初期化されていない派生インスタンスで呼び出すことができます。

したがって、クラス不変条件に対するこのC ++言語のサポートは非​​常に優れており、多くのチェックと多くの苛立たしい厄介なバグをなくすのに役立ちます。

ただし、基本クラスのコンストラクターで派生クラス固有の初期化を行うのは少し難しくなります。たとえば、最上位のGUIWidgetクラスのコンストラクターで一般的なことを行う場合などです。

FAQ項目「わかりました。ただし、基本クラスのコンストラクター内でこのオブジェクトに対して動的バインディングが機能するかのように、その動作をシミュレートする方法はありますか?」少し入ります。

最も一般的なケースのより完全な扱いについては、私のブログ記事「パーツファクトリを使用して建設後を回避する方法」も参照してください。

于 2012-11-18T13:27:34.350 に答える
7

実装方法に関係なく、言語定義が何をすべきかの違いです。Java では、完全に初期化されていない派生オブジェクト (ゼロで初期化されていますが、そのコンストラクターは実行されていません) で関数を呼び出すことができます。C++ はそれを許可しません。派生クラスのコンストラクターが実行されるまで、派生クラスはありません。

于 2012-11-18T13:13:52.877 に答える
2

うまくいけば、これが役立ちます:

new Derived()が実行されると、最初にメモリ割り当てが行われます。Baseプログラムは、との両方のメンバーを保持するのに十分な大きさのメモリのチャンクを割り当てDerrivedます。この時点では、オブジェクトはありません。初期化されていないメモリです。

のコンストラクターが完了するBaseと、メモリにはタイプ のオブジェクトが含まれ、Baseのクラス不変条件Baseが保持されます。Derivedその記憶にはまだオブジェクトがありません。

ベースの構築Base、オブジェクトは部分的に構築された状態にありますが、言語ルールは、部分的に構築されたオブジェクトで独自のメンバー関数を呼び出すことができるほど十分に信頼しています。Derivedオブジェクトは部分的に構築されていません。存在しません。

仮想関数への呼び出しは、その時点でBaseオブジェクトの最も派生した型であるため、基本クラスのバージョンを呼び出すことになります。を呼び出すと、タイプ セーフではない this-pointer を使用してDerived::virtのメンバー関数を呼び出すことになり、DerivedDerrivedタイプ セーフが破られます。

論理的には、クラスは構築され、呼び出された関数を持ち、その後破棄されるものです。構築されていないオブジェクトでメンバー関数を呼び出すことはできません。また、破棄された後にオブジェクトでメンバー関数を呼び出すことはできません。これは OOP にとってかなり基本的なことです。C++ 言語の規則は、このモデルを壊すようなことを避けるのに役立つだけです。

于 2012-11-18T14:28:55.170 に答える
0

実際、私はこの設計決定の背後にある洞察が何であるかを知りたいです

Javaでは、すべてのタイプがObjectから派生し、すべてのObjectが何らかのリーフタイプであり、すべてのオブジェクトが構築される単一のJVMがある可能性があります。

C ++では、多くのタイプは仮想ではありません。さらに、C ++では、基本クラスとサブクラスを別々にマシンコードにコンパイルできます。したがって、基本クラスは、他のスーパークラスであるかどうかに関係なく、実行することを実行します。

于 2012-11-18T13:22:00.317 に答える
0

Javaでは、メソッドの呼び出しはオブジェクトタイプに基づいているため、そのように動作します(c ++についてはよくわかりません)。

ここで、オブジェクトはタイプDerivedであるため、jvmはDerivedオブジェクトに対してメソッドを呼び出します。

仮想の概念を明確に理解している場合、Javaで同等のものは抽象的ですが、現在のコードはJava用語では実際には仮想コードではありません。

何か問題があれば、私の答えを更新してください。

于 2012-11-18T13:11:44.503 に答える
0
于 2016-09-22T08:06:16.387 に答える