2016-05-03 10 views
5

クラス「Animal」とサブクラス「Cat」と「Dog」があるとします。仮想メソッドをそのように宣言しなければならない理由

「動物」の中間関数にオブジェクトを渡すときに、「Cat」と「Dog」の両方でノイズを発生させたいとします(cat: "meow" - dog: "woof")。

なぜこれを行うには仮想メソッドを使用する必要がありますか? "Animal"で仮想メソッドを定義せずにAnimal-> makeNoise()を実行できませんでしたか? "Cat"と "Dog"はどちらも動物であるため、 "makeNoise()"は関数に渡されたAnimalを参照していることは明らかですか?

これは単なる文法などの問題ですか?私はJavaでこれを行う必要はないと確信しています。

+4

@Antonioでは、仮想基本クラスは仮想メソッドとはまったく関係ありません。これは、C++がいくつかの目的でキーワードを悪用するうえで非常にうまく機能するケースの1つです。 –

+0

'makeNoise()'メソッドを持たないラットを渡すとどうなりますか? –

答えて

10

Javaでは、すべてのメンバー関数はデフォルトでvirtualstatic,private、およびfinal個を除く)です。

C++では、すべてのメンバー関数はデフォルトでvirtualではありません。機能を作ることvirtualは、ランタイムとオブジェクトサイズの両方のオーバーヘッドを追加します.C++の考え方は、あなたが使っていないものを支払うことはありません。私のオブジェクトのほとんどは多型ではありませんので、必要がない限り多形性を支払う必要はありません。 Animal::makeNoise()virtualである必要があるため、明示的に指定する必要があります。

+0

Javaでは、 'final'関数は、それが基本クラスで' final'ではない場合、仮想である可能性があります。つまり、クラスは仮想関数をオーバーライドし、それ以上の上書きを防ぐために最終的にすることができますが、それでも階層ツリーを仮想的に上にしています。 –

+0

また、非仮想関数はパフォーマンスの向上だけでなく、ある程度の安全性も提供すると言及するべきです。たとえば、コンストラクタから非仮想関数を呼び出すことは安全ですが、コンストラクタから仮想関数を呼び出すことは一般的に悪い考えです(JavaではC++ではそれほど悪くないが)。 –

+0

C++では、あなたが使っていないものを支払うことはありません。 Javaではすべての費用がかかります。 –

0

Javaでは、仮想メソッドも使用します。 これは、ソフトウェアの疎結合を改善します。

たとえば、ライブラリを使用して、内部で使用する動物を知ることはできません。あなたが知らない動物のインプリメンテーションがあり、それは動物なので使用することができます。あなたは、library.getAnimalメソッドで動物を取得します。これでmakeNoiseメソッドを実装する必要があるので、どのノイズが発生しているのかわからなくても、その動物を使うことができます。

編集:あなたの質問に答えるために、C++は明示的な宣言を望んでおり、javaでは暗黙的です。そう、それは言語特有の特異性の一種です。

+0

Javaではこれをしないので、これを行うことはできません。 Javaにそのようなキーワードがないので、仮想関数を*宣言することはできません。 –

+0

Javaの親メソッドをオーバーライドすることはできませんか? Javaのパブリックメソッドはデフォルトで仮想ですimho – ChampS

+0

そうです。デフォルトでは仮想的なので、仮想として宣言することも、そうする必要もありません。質問を注意深く読んでください。 –

0

これは言語のルールの1つなので、あなたはそれをしなければならないと言えるでしょう。

それは理由があります。

Animalを使用するコードの検証を試みるとき、コンパイラはAnimalにどのような関数が存在するかを知っています。動物から派生したすべてのクラスをチェックせずにコードが正しいかどうかを知ることが可能です。したがって、そのコードは、それらの派生クラスすべてに依存する必要はありません。 Animalから新しいクラスを派生させたが、新しいクラスのエラーであるmakeNoise関数を実装することを忘れた場合、Animal基本クラスを使用するコードではなく、コンパイラがそのエラーを指摘できます。 Animalに仮想関数が宣言されていなければ、呼び出し元のコードか新しいクラスがエラーかどうかを判断する方法はありません。

ここで重要なポイントは、静的型付けのために、これらのエラーがC++のコンパイル時に検出されることです。他の言語では動的な型指定が可能ですが、いくつか簡単にすることができますが、エラーは実行時にしか検出されません。

0

Javaでは、すべての機能がデフォルトで仮想です。 C++ではそうではありません。したがって、指定された型のポインタで非仮想関数を呼び出すと、オブジェクトのアドレスがthisでその型の関数の実装が呼び出されます。

class Animal { 
public: 
    void sound() { std::cout << "splat\n"; } 
    virtual void appearance() { std::cout << "animaly\n"; } 
}; 

class Cat { 
public: 
    void sound() { std::cout << "meow\n"; } 
    virtual void appearance() { std::cout << "furry\n"; } 
}; 

int main() { 
    Animal a; 
    Cat c; 
    Animal* ac = new Cat; 

    a.sound(); // splat 
    a.appearance(); // animaly 

    c.sound(); // meow 
    c.appearance(); // furry 

    ac->sound(); // splat 
    ac->appearance(); // furry 
} 

あなたではなく、特定の派生クラスのポインタを必要とするよりも、「動物」に一般化関数を記述したい場合に発生します。

0

C++は、できるだけオーバーヘッドを少なくして実行するように設計されており、プログラマが正しい呼び出しを行うことを信頼します。基本的に、私の友人の一人が頻繁に言うことを好むので、それは 'あなたに銃と足で自分を撃つための選択肢を与えます。スピードと柔軟性が最優先です。

真のポリモーフィックな振る舞いを正しく引き起こすためには、C++ではそれを指定する必要があります。しかしながら!すべての派生クラスは仮想メンバ関数を継承するため、基本クラスでのみ指定する必要があります。メンバが仮想メンバ関数を継承する場合は、宣言に 'virtual'を配置することをお勧めしますが、必須ではありません。

通常、ADTは純粋な仮想関数を実装して、派生クラスが関数を実装しなければならないことを示します。以下のような:再び

animal makeNoise() = 0; /*Indicates this function contains no implementation. 
and must be implemented by derived classes in order to function.*/ 

、派生クラスであれば、基底クラスがこれを含んでいるとして、その継承されたメンバーに「仮想」を含める必要はありません。

1

動物の種類を推定してからmake_sound()を呼び出す場合は、動物のすべての子供の動物オブジェクトに対してdynamic_castを実行する必要があります。これには、直接的または間接的にAnimalクラスの子クラスであるクラスが含まれます。

これは、維持するのが難しく、変更に非常に苦痛です。新しいクラスを子としてAnimalクラスに追加します。

C++の考え方は効率的なので、実行時の多形性を高価なものとして提供するようにコンパイラに依頼する必要があります。あなたはどうしますか?仮想化としてmake_sound()関数を記述することによって。これは、オブジェクトの型に基づいて異なるmake_sound()のアドレスを参照するvtable(関数ポインタのテーブル)を作成します。

間接指示がすべてあなたのために処理されるので、ダウンキャストする必要はありません。何百行ものコードは、単一行のコードに過ぎません。それは間接的な力です!

関連する問題