関数が仮想として定義されている場合、それは純粋な仮想と同じであるとはどういう意味ですか?
12 に答える
ウィキペディアの仮想関数から ...
C++ や Object Pascal などの言語のオブジェクト指向プログラミングでは、仮想関数または仮想メソッドは、動的ディスパッチが容易になる継承およびオーバーライド可能な関数またはメソッドです。この概念は、オブジェクト指向プログラミング (OOP) の (ランタイム) ポリモーフィズム部分の重要な部分です。つまり、仮想関数は実行されるターゲット関数を定義しますが、ターゲットはコンパイル時に認識されない場合があります。
非仮想関数とは異なり、仮想関数がオーバーライドされると、最も派生したバージョンが、それが作成されたレベルだけでなく、クラス階層のすべてのレベルで使用されます。したがって、基本クラスの 1 つのメソッドが仮想メソッドを呼び出す場合、基本クラスで定義されたバージョンではなく、派生クラスで定義されたバージョンが使用されます。
これは、派生クラスでオーバーライドできる非仮想関数とは対照的ですが、「新しい」バージョンは派生クラス以下でのみ使用され、基本クラスの機能はまったく変更されません。
一方..
純粋仮想関数または純粋仮想メソッドは、派生クラスが抽象クラスでない場合に派生クラスによって実装する必要がある仮想関数です。
純粋仮想メソッドが存在する場合、クラスは「抽象」であり、単独でインスタンス化することはできません。代わりに、純粋仮想メソッドを実装する派生クラスを使用する必要があります。純粋仮想は基本クラスでまったく定義されていないため、派生クラスで定義する必要があります。そうしないと、その派生クラスも抽象であり、インスタンス化できません。抽象メソッドを持たないクラスのみインスタンス化できます。
仮想は基本クラスの機能をオーバーライドする方法を提供し、純粋仮想はそれを必要とします。
ここでいくつか繰り返されているように、ウィキペディアの仮想の定義についてコメントしたいと思います。[この回答が書かれた時点で]ウィキペディアは、仮想メソッドをサブクラスでオーバーライドできるメソッドとして定義しました。[幸いなことに、ウィキペディアはそれ以来編集されており、これを正しく説明しています。]これは正しくありません。仮想メソッドだけでなく、任意のメソッドをサブクラスでオーバーライドできます。仮想が行うことは、多型性、つまり、実行時にメソッドの最も派生したオーバーライドを選択する機能を提供することです。
次のコードを検討してください。
#include <iostream>
using namespace std;
class Base {
public:
void NonVirtual() {
cout << "Base NonVirtual called.\n";
}
virtual void Virtual() {
cout << "Base Virtual called.\n";
}
};
class Derived : public Base {
public:
void NonVirtual() {
cout << "Derived NonVirtual called.\n";
}
void Virtual() {
cout << "Derived Virtual called.\n";
}
};
int main() {
Base* bBase = new Base();
Base* bDerived = new Derived();
bBase->NonVirtual();
bBase->Virtual();
bDerived->NonVirtual();
bDerived->Virtual();
}
このプログラムの出力は何ですか?
Base NonVirtual called.
Base Virtual called.
Base NonVirtual called.
Derived Virtual called.
Derivedは、Baseのすべてのメソッドをオーバーライドします。仮想メソッドだけでなく、非仮想メソッドもオーバーライドします。
Base-pointer-to-Derived(bDerived)がある場合、NonVirtualを呼び出すとBaseクラスの実装が呼び出されることがわかります。これはコンパイル時に解決されます。コンパイラは、bDerivedがBase *であり、NonVirtualが仮想ではないことを認識しているため、クラスBaseで解決を行います。
ただし、Virtualを呼び出すと、Derivedクラスの実装が呼び出されます。キーワードvirtualのため、メソッドの選択はコンパイル時ではなく実行時に行われます。コンパイル時にここで何が起こるかというと、コンパイラはこれがBase *であり、仮想メソッドを呼び出していることを認識しているため、Baseクラスではなくvtableへの呼び出しを挿入します。このvtableは実行時にインスタンス化されるため、実行時の解決は最も派生したオーバーライドになります。
これがあまり混乱していなかったと思います。つまり、どのメソッドもオーバーライドできますが、ポリモーフィズム、つまり、最も派生したオーバーライドの実行時の選択を提供するのは仮想メソッドだけです。ただし、実際には、非仮想メソッドをオーバーライドすることは悪い習慣と見なされ、ほとんど使用されないため、多くの人(Wikipediaの記事を書いた人を含む)は、仮想メソッドのみをオーバーライドできると考えています。
virtualキーワードは、C++にポリモーフィズムをサポートする機能を提供します。次のようなクラスのオブジェクトへのポインタがある場合:
class Animal
{
public:
virtual int GetNumberOfLegs() = 0;
};
class Duck : public Animal
{
public:
int GetNumberOfLegs() { return 2; }
};
class Horse : public Animal
{
public:
int GetNumberOfLegs() { return 4; }
};
void SomeFunction(Animal * pAnimal)
{
cout << pAnimal->GetNumberOfLegs();
}
この(ばかげた)例では、GetNumberOfLegs()関数は、呼び出されたオブジェクトのクラスに基づいて適切な番号を返します。
ここで、関数「SomeFunction」について考えてみましょう。動物から派生している限り、どのタイプの動物オブジェクトが渡されるかは関係ありません。コンパイラは、Animalから派生したクラスを基本クラスであるため、自動的にAnimalにキャストします。
これを行う場合:
Duck d;
SomeFunction(&d);
「2」を出力します。これを行う場合:
Horse h;
SomeFunction(&h);
「4」を出力します。これはできません:
Animal a;
SomeFunction(&a);
GetNumberOfLegs()仮想関数が純粋であるためにコンパイルされないためです。つまり、クラス(サブクラス)を派生させることによって実装する必要があります。
純粋仮想関数は、主に以下を定義するために使用されます。
a)抽象クラス
これらは基本クラスであり、それらから派生して、純粋仮想関数を実装する必要があります。
b)インターフェース
これらは「空の」クラスであり、すべての関数が純粋な仮想であるため、すべての関数を派生させて実装する必要があります。
C++ クラスでは、virtualは、メソッドをサブクラスでオーバーライド (つまり、実装) できることを示すキーワードです。例えば:
class Shape
{
public:
Shape();
virtual ~Shape();
std::string getName() // not overridable
{
return m_name;
}
void setName( const std::string& name ) // not overridable
{
m_name = name;
}
protected:
virtual void initShape() // overridable
{
setName("Generic Shape");
}
private:
std::string m_name;
};
この場合、サブクラスはinitShape関数をオーバーライドして、特殊な作業を行うことができます。
class Square : public Shape
{
public:
Square();
virtual ~Square();
protected:
virtual void initShape() // override the Shape::initShape function
{
setName("Square");
}
}
純粋仮想という用語は、サブクラスによって実装される必要があり、基本クラスによって実装されていない仮想関数を指します。virtualキーワードを使用し、メソッド宣言の最後に =0を追加して、メソッドを純粋仮想として指定します。
したがって、Shape::initShape を純粋な仮想にしたい場合は、次のようにします。
class Shape
{
...
virtual void initShape() = 0; // pure virtual method
...
};
クラスに純粋仮想メソッドを追加することで、クラスを抽象基本クラスにすることができ ます。これは、インターフェイスを実装から分離するのに非常に便利です。
仮想キーワードはどのように機能しますか?
Man が基底クラスで、Indian が man から派生したものであるとします。
Class Man
{
public:
virtual void do_work()
{}
}
Class Indian : public Man
{
public:
void do_work()
{}
}
do_work() を仮想として宣言するということは、単純に、どの do_work() を呼び出すかは、実行時にのみ決定されることを意味します。
私がそうすると仮定すると、
Man *man;
man = new Indian();
man->do_work(); // Indian's do work is only called.
virtual が使用されていない場合、呼び出しているオブジェクトに応じて、同じものが静的に決定されるか、コンパイラによって静的にバインドされます。したがって、Man のオブジェクトが do_work() を呼び出す場合、Man の do_work() は、それがインドのオブジェクトを指していても呼び出されます。
一番多く投票された回答は誤解を招くものだと思います - 仮想かどうかに関係なく、派生クラスでオーバーライドされた実装を持つことができるメソッド。C++ を具体的に参照すると、正しい違いは、関連する関数の実行時 (virtual が使用されている場合) バインディングとコンパイル時 (virtual が使用されていないが、メソッドがオーバーライドされ、ベース ポインターが派生オブジェクトを指している場合) バインディングです。
という別の誤解を招くコメントがあるようです。
「ジャスティン、「純粋な仮想」は、「この関数は基本クラスでは実装できない」という意味で使用される単なる用語です (キーワードではありません。以下の私の回答を参照してください)。
これは間違っています!純粋な仮想関数も本体を持つことができ、実装することができます! 真実は、抽象クラスの純粋仮想関数を静的に呼び出すことができるということです! 2 人の非常に優れた著者は、Bjarne Stroustrup と Stan Lippman です....彼らは言語を書いたからです。
デフォルトで静的メソッド バインディングを使用する Simula、C++、および C# では、プログラマは、特定のメソッドを仮想としてラベル付けすることにより、動的バインディングを使用する必要があることを指定できます。動的メソッド バインディングは、オブジェクト指向プログラミングの中心です。
オブジェクト指向プログラミングには、カプセル化、継承、動的メソッド バインディングという 3 つの基本概念が必要です。
カプセル化により、抽象化の実装の詳細を単純なインターフェイスの背後に隠すことができます。
継承により、新しい抽象化を既存の抽象化の拡張または改良として定義し、その特性の一部またはすべてを自動的に取得できます。
動的メソッド バインディングを使用すると、古い抽象化が必要なコンテキストで使用された場合でも、新しい抽象化が新しい動作を表示できます。
仮想メソッドは派生クラスによってオーバーライドできますが、基本クラス (オーバーライドされるクラス) での実装が必要です。
純粋仮想メソッドには、基本クラスの実装がありません。それらは派生クラスによって定義される必要があります。(つまり、オーバーライドするものがないため、技術的にオーバーライドされたというのは適切な用語ではありません)。
Virtual は、派生クラスが基本クラスのメソッドをオーバーライドする場合のデフォルトの Java 動作に対応します。
Pure Virtual メソッドは、抽象クラス内の抽象メソッドの動作に対応します。また、純粋な仮想メソッドと定数のみを含むクラスは、インターフェイスの cpp ペンダントになります。
純粋仮想機能
このコードを試してください
#include <iostream>
using namespace std;
class aClassWithPureVirtualFunction
{
public:
virtual void sayHellow()=0;
};
class anotherClass:aClassWithPureVirtualFunction
{
public:
void sayHellow()
{
cout<<"hellow World";
}
};
int main()
{
//aClassWithPureVirtualFunction virtualObject;
/*
This not possible to create object of a class that contain pure virtual function
*/
anotherClass object;
object.sayHellow();
}
クラスanotherClassで関数 sayHellow を削除し、コードを実行します。エラーが発生します。クラスに純粋な仮想関数が含まれている場合、そのクラスからオブジェクトを作成できず、継承されるため、派生クラスはその関数を実装する必要があります。
仮想機能
別のコードを試す
#include <iostream>
using namespace std;
class aClassWithPureVirtualFunction
{
public:
virtual void sayHellow()
{
cout<<"from base\n";
}
};
class anotherClass:public aClassWithPureVirtualFunction
{
public:
void sayHellow()
{
cout<<"from derived \n";
}
};
int main()
{
aClassWithPureVirtualFunction *baseObject=new aClassWithPureVirtualFunction;
baseObject->sayHellow();///call base one
baseObject=new anotherClass;
baseObject->sayHellow();////call the derived one!
}
ここで、sayHellow関数は基本クラスで仮想としてマークされています.派生クラスで関数を検索して関数を実装しようとするコンパイラを言います.見つからない場合は、基本クラスを実行します.ありがとう
「仮想関数または仮想メソッドは、同じシグネチャを持つ関数によって継承クラス内で動作をオーバーライドできる関数またはメソッドです」- ウィキペディア
これは、仮想関数の適切な説明ではありません。メンバーが仮想でなくても、継承クラスはそれをオーバーライドできるためです。あなたはそれを自分で試してみることができます。
違いは、関数が基本クラスをパラメーターとして受け取る場合に現れます。継承クラスを入力として指定すると、その関数はオーバーライドされた関数の基本クラスの実装を使用します。ただし、その関数が仮想の場合は、派生クラスに実装されている関数が使用されます。