javaの最終メソッドとc ++の非仮想メソッドは異なりますか、それとも同じですか? どのように?
4 に答える
それらは違う。
C++ の非仮想メソッドはディスパッチされず、何もオーバーライドされません。
Java final メソッドがディスパッチされ、そのクラスのスーパークラスでメソッドをオーバーライドできます。
ただし、C++ の非仮想メソッドも Java の最終メソッドもオーバーライドできないという点では似ています。これらは、静的型が問題の型であるオブジェクトがある場合、ランタイム システムがメソッド呼び出しをディスパッチする必要がないという意味でも似ています。
違いを説明するために、次の 2 つの Java クラスを考えてみましょう。
public class A {
public String toString() {
return "A";
}
}
public class B extends A {
public final String toString() {
return "B";
}
}
A a = ...
B b = ...
a.toString(); // could call A.toString() or B.toString() - dispatch
b.toString(); // could only call B.toString() - no dispatch required
// but it will behave the same as if dispatching had occurred.
B::toString() が非仮想である C++ に相当するものでは、 にa.toString()
ディスパッチできなかったと思いますB::toString()
。(私は C++ に少し慣れていません...)
(実際、Java JIT コンパイラーは、クラスまたはメソッドを として宣言しなくても、仮想ディスパッチが不要なケースを検出することができますfinal
。したがって、の真の目的はfinal
、メソッドをオーバーライドしてはならない、またはクラスをオーバーライドしてはならないことを指定することです。拡張しないでください...そしてJavaコンパイラにこれをチェックさせてください.)
C++ でクラスを継承する際に、同じシグネチャを使用して非仮想メンバー関数を宣言することはできますが、Java は、基本クラスがメソッド final を宣言するのと同じシグネチャを使用してメソッドを宣言することを明示的に禁止しています。C++ の仮想性は、継承/ポリモーフィズムを処理するときに呼び出す正しい関数を見つけるのに役立ちます。
例:
#include <iostream>
class Base
{
public:
void doIt()
{
std::cout << "from Base.doIt()" << std::endl;
}
};
class Child : public Base
{
public:
void doIt()
{
std::cout << "from Child.doIt()" << std::endl;
}
};
int main()
{
Base a;
a.doIt(); // calls Base.doIt()
Child b;
b.doIt(); // calls Child.doIt()
Base *c = new Base();
c->doIt(); // calls Base.doIt()
Child *d = new Child();
d->doIt(); // calls Child.doIt()
Base *e = new Child();
e->doIt(); // calls Base.doIt()
std::cin.ignore();
return 0;
}
final を使用した Java の同等の例では、コンパイラ エラーが発生します。
public class Base
{
public final void doIt()
{
System.out.println("In Base.doIt()");
}
}
public class Child extends Base
{
public void doIt() // compiler error: Cannot overload the final method from Base
{
System.out.println("In Child.doIt()");
}
}
C++ のポリモーフィズムの詳細については、cplusplus.com: ポリモーフィズムを参照してください。
ただし、実質的には、両方のメソッドの目的は似ています。つまり、基本クラスでの関数のオーバーライドを防ぐことです。彼らは、わずかに異なる方法でそれを行っているだけです。
それらは非常に異なっており、実際、完全に無関係です。
C++ では、基底クラスに非仮想メンバー関数がある場合、派生クラスで同じ名前の非仮想メンバー関数を宣言できます。その結果、派生クラスのメンバー関数が基本クラスのメンバー関数を隠します。また、仮想ディスパッチは発生しません。次のように:
struct Base {
void foo() {
std::cout << "Base::foo called!" << std::endl;
};
};
struct Derived : Base {
void foo() {
std::cout << "Derived::foo called!" << std::endl;
};
};
int main() {
Derived d;
d.foo(); //OUTPUT: "Derived::foo called!"
Base* b = &d;
b->foo(); //OUTPUT: "Base::foo called!"
};
上記は、派生クラスのメンバー関数が基本クラス関数を非表示にする方法を示しています。基本クラスへのポインターがある場合、関数は非仮想であるため、仮想テーブルは呼び出しの解決に使用されないため、基本クラスの foo 関数が呼び出されます。ここでのポイントは、C++ では、Derived クラスで同じ名前の別の関数を作成することを妨げるものは何もないということです (別のシグネチャを使用すると、基本クラスの同じ名前のすべてのメンバー関数が引き続き非表示になることに注意してください)。表示されるのは、Derived クラスのメンバー関数が Base クラスのメンバー関数を隠していることを知らせるコンパイラ警告だけです。
Java の final メンバー関数はまったく異なります。Java では、すべてのメンバー関数は仮想です。したがって、C++ のように仮想ディスパッチをオフにすることはできません。最終メンバー関数は、後続の派生クラスが同じ名前 (および署名) を持つメンバー関数を宣言することを許可されない (エラーが発生する) ことを意味します。ただし、元のメンバー関数を宣言したインターフェイス/基本クラスと、それを final としてマークした派生クラスの間で、仮想ディスパッチ (したがって、動的にポリモーフィックな意味でのオーバーライド) が引き続き発生します。後で関数をオーバーライドすることは固く禁じられているということだけです (つまり、基本クラスで final としてマークされた foo() を使用して上記のコードを試すと、派生クラスの宣言でエラーが発生します。許可された)。
ご覧のとおり、2 つの概念はまったく異なります。
- C++ では、非仮想メンバー関数を使用すると、仮想ディスパッチは発生しません (したがって、従来の意味での「オーバーライド」はありません) が、同じ名前のメンバー関数を持つ派生クラスを使用することができます (また、場合によっては便利です)。 「静的ポリモーフィズム」で)。
- Java では、final メンバー関数を使用しても仮想ディスパッチが発生しますが、後続の派生クラスでのオーバーライドは厳密に禁止されています。
仮想関数と非仮想関数を使用すると、C++ ではパフォーマンスに違いが生じる可能性がありますが、逆に Java ではパフォーマンスに違いがない場合があります。Java では、final
純粋にコードの明快さと保守性に関するものとしてメソッドをマークします (これはデフォルトの動作ではなく、比較的めったに使用されません)。C++ では、非仮想関数はデフォルトの動作であり、パフォーマンスが向上するため、部分的に一般的に使用されます。特徴。
Java では、生成されるコードは使用方法によって異なる可能性がありますが、C++ ではコンパイル時に正しいコードを生成する必要があります。
たとえば、JVM は、「仮想」メソッドに共通して使用される実装が 1 つまたは 2 つしかないことを検出した場合、それらのメソッドをインライン化するか、1 つの実装のみを使用する「仮想」メソッドを最終的なものとして扱うことができます。