仮想/純粋仮想の説明


345

関数が仮想として定義されているとはどういう意味ですか?それは純粋な仮想と同じですか?

回答:


338

ウィキペディアの仮想機能 ...

オブジェクト指向プログラミングでは、C ++やObject Pascalなどの言語では、仮想関数または仮想メソッドは、動的ディスパッチが容易になる継承可能でオーバーライド可能な関数またはメソッドです。この概念は、オブジェクト指向プログラミング(OOP)の(実行時の)ポリモーフィズム部分の重要な部分です。つまり、仮想関数は実行するターゲット関数を定義しますが、ターゲットはコンパイル時に認識されない場合があります。

非仮想関数とは異なり、仮想関数がオーバーライドされると、作成されたレベルだけでなく、クラス階層のすべてのレベルで最も派生したバージョンが使用されます。したがって、基本クラスの1つのメソッドが仮想メソッドを呼び出す場合、基本クラスで定義されたバージョンの代わりに、派生クラスで定義されたバージョンが使用されます。

これは、派生クラスでオーバーライドできる非仮想関数とは対照的ですが、「新しい」バージョンは派生クラス以下でのみ使用され、基本クラスの機能はまったく変更されません。

一方..

純粋仮想関数または純粋仮想メソッドは、派生クラスが抽象でない場合に、派生クラスによって実装される必要がある仮想関数です。

純粋な仮想メソッドが存在する場合、クラスは「抽象的」であり、それ自体ではインスタンス化できません。代わりに、純粋仮想メソッドを実装する派生クラスを使用する必要があります。純粋仮想は基本クラスでまったく定義されていないため、派生クラスで定義する必要があります。そうでない場合、その派生クラスも抽象化されており、インスタンス化できません。インスタンス化できるのは、抽象メソッドを持たないクラスだけです。

virtualは基本クラスの機能をオーバーライドする方法を提供し、pure-virtual それを必要とします。


10
それで...純粋に仮想のキーワードなのか、それとも使われている言葉なのか?
ジャスティン

197
virtual void Function()= 0; 純粋な仮想です。「= 0」は純粋であることを示します。
Goz、

8
ジャスティン、「純粋な仮想」は単に「この関数は基本クラスでは実装できない」という意味の用語(キーワードではなく、以下の私の回答を参照)です。ゴズが言ったように、仮想の末尾に「= 0」を追加します関数はそれを「純粋」にします
ニック・ハダード2008

14
Stroustrupはpureキーワードを追加したいと言ったが、Bell LabsがC ++のメジャーリリースを作成しようとしており、彼のマネージャーはその後半の段階ではそれを許可しないと言っていたと思います。キーワードを追加するのは大変なことです。
クォーク

14
これは良い答えではありません。仮想メソッドだけでなく、任意のメソッドをオーバーライドできます。詳細については、私の回答を参照してください。
Asik、2014

212

ウィキペディアの仮想の定義についてコメントしたいと思います。[この回答が書かれた時点で]ウィキペディアは、仮想メソッドをサブクラスでオーバーライドできるものとして定義しました。[幸いなことに、ウィキペディアは編集されており、これで正しく説明されています。]これは誤りです。仮想メソッドだけでなく、すべてのメソッドをサブクラスでオーバーライドできます。virtualが行うことは、ポリモーフィズム、つまり、メソッドの最も派生したオーバーライドを実行時に選択する機能を提供することです。

次のコードを検討してください。

#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の記事を書いた人を含む)は、仮想メソッドのみをオーバーライドできると考えています。


6
Wikipediaの記事(私が守る方法ではありません)が仮想メソッドを「サブクラスでオーバーライドできるものとして」定義しているからといって、同じ名前を持つ他の非仮想メソッドを宣言できる可能性は排除されません。これはオーバーロードとして知られています。

26
それでも定義は正しくありません。派生クラスでオーバーライドできるメソッドは、定義上仮想ではありません。メソッドをオーバーライドできるかどうかは、「仮想」の定義には関係ありません。また、「オーバーロード」は通常、同じクラスに同じ名前と戻り値の型を持つが引数の異なる複数のメソッドを持つことを指します。まったく同じシグネチャを意味する「オーバーライド」とは大きく異なりますが、派生クラスにあります。非多態的(非仮想ベース)に行われる場合、「非表示」と呼ばれることがあります。
Asik 2009

5
これは受け入れられる答えになるはずです。この質問に関して他の誰もそれをしていないので、ここにリンクするのに時間をかけるその特定のウィキペディアの記事は、完全なゴミです。+1、良い先生。
josaphatv 2014年

2
今それは理にかなっています。すべてのメソッドが派生クラスによってオーバーライドされる可能性があることを正しく説明していただき、ありがとうございます。変更は、さまざまな状況で呼び出される関数を選択するためのコンパイラの動作方法にあります。
Doodad

3
Derived*ポイントをホームにドライブするために同じ関数呼び出しでを追加すると役立つ場合があります。そうでなければ素晴らしい答え
ジェフ・ジョーンズ

114

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'を考えます。動物に由来する限り、どのような種類の動物オブジェクトが渡されるかは関係ありません。コンパイラーは、アニマル派生クラスをアニマルに基本クラスとして自動的にキャストします。

これを行うと:

Duck d;
SomeFunction(&d);

「2」を出力します。これを行うと:

Horse h;
SomeFunction(&h);

それは「4」を出力します。これはできません:

Animal a;
SomeFunction(&a);

これは、GetNumberOfLegs()仮想関数が純粋であるためにコンパイルされないためです。つまり、派生クラス(サブクラス)によって実装する必要があります。

純粋仮想関数は、主に次の定義に使用されます。

a)抽象クラス

これらは、それらから派生し、純粋な仮想関数を実装する必要がある基本クラスです。

b)インターフェース

これらは「空の」クラスであり、すべての関数は純粋な仮想であるため、すべての関数を導出して実装する必要があります。


あなたの例では、純粋な仮想メソッドの実装を提供していないため、#4を実行できません。メソッドは純粋に仮想であるため、厳密ではありません。
iheanyi 2014

@iheanyi基本クラスの純粋仮想メソッドに実装を提供することはできません。したがって、ケース#4はまだエラーです。
prasad

32

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
 ... 
};

純粋な仮想メソッドをクラスに追加することにより、クラスを抽象基本クラスにし て、インターフェイスを実装から分離するのに非常に便利です。


1
「サブクラスで実装する必要がある仮想関数」については、厳密には真実ではありませんが、そうでない場合、サブクラスも抽象的です。また、抽象クラスはインスタンス化できません。また、「基本クラスでは実装できない」というのは誤解を招くようです。基本クラス内に実装を追加するためのコードの変更に制限がないため、「行ったことがない」のが良いでしょう。
NVRAM

2
また、「getName関数をサブクラスで実装することはできません」は、正しくありません。サブクラスはメソッドを(同じまたは異なるシグネチャで)実装できますが、その実装はメソッドをオーバーライドしません。Circleをサブクラスとして実装し、 "std :: string Circle :: getName()"を実装できます。その後、Circleインスタンスのいずれかのメソッドを呼び出すことができます。ただし、Shapeポインターまたは参照を介して使用する場合、コンパイラーはShape :: getName()を呼び出します。
NVRAM

1
両方の面で良い点。私はこの例の特別なケースの議論を避けようとしていました。もっと寛容になるように答えを変更します。ありがとう!
Nick Haddad

@NickHaddad古いスレッドですが、なぜ変数を呼び出したのでしょうかm_name。どういうm_意味ですか?
Tqn 2014

1
@Tqnは、NickHaddadが規則に従っていると仮定すると、m_nameは一般にハンガリー語表記と呼ばれる命名規則です。mは、構造体/クラスのメンバー、整数を示します。
Ketcomp 2015

16

「仮想」とは、メソッドがサブクラスでオーバーライドされる可能性があるが、基本クラスで直接呼び出し可能な実装があることを意味します。「純粋仮想」とは、直接呼び出し可能な実装がない仮想メソッドであることを意味します。このようなメソッドは、継承階層で少なくとも1回オーバーライドする必要あります。クラスに実装されていない仮想メソッドがある場合、そのクラスのオブジェクトは構築できず、コンパイルは失敗します。

@quarkは、純粋仮想メソッドに実装を含めることできることを指摘していますが、純粋仮想メソッドをオーバーライドする必要があるため、デフォルトの実装を直接呼び出すことはできません。以下は、デフォルトの純粋仮想メソッドの例です。

#include <cstdio>

class A {
public:
    virtual void Hello() = 0;
};

void A::Hello() {
    printf("A::Hello\n");
}

class B : public A {
public:
    void Hello() {
        printf("B::Hello\n");
        A::Hello();
    }
};

int main() {
    /* Prints:
           B::Hello
           A::Hello
    */
    B b;
    b.Hello();
    return 0;
}

コメントによると、コンパイルが失敗するかどうかはコンパイラ固有です。GCC 4.3.3以降では、コンパイルできません。

class A {
public:
    virtual void Hello() = 0;
};

int main()
{
    A a;
    return 0;
}

出力:

$ g++ -c virt.cpp 
virt.cpp: In function int main()’:
virt.cpp:8: error: cannot declare variable a to be of abstract type A
virt.cpp:1: note:   because the following virtual functions are pure within A’:
virt.cpp:3: note:   virtual void A::Hello()

クラスのインスタンスをインスタンス化する場合は、オーバーライドする必要があります。インスタンスを作成しない場合、コードは正常にコンパイルされます。
グレン

1
コンパイルは失敗しません。(純粋な)仮想メソッドの実装がない場合、そのクラス/オブジェクトはインスタンス化できません。リンクできませんが、コンパイルされます。
ティム

@ Glen、@ tim:どのコンパイラで?抽象クラスを構築するプログラムをコンパイルしようとしても、コンパイルできません。
ジョンミリキン、

@Johnコンパイルは、PVFを含むクラスのインスタンスをインスタンス化しようとした場合にのみ失敗します。もちろん、そのようなクラスのポインタまたは参照値をインスタンス化できます。

5
また、ジョン、次は正しくありません:「「純粋な仮想」は、実装のない仮想メソッドであることを意味します。」純粋仮想メソッドは実装持つことできます。ただし、それらを直接呼び出すことはできません。サブクラス内から基本クラスの実装をオーバーライドして使用する必要があります。これにより、実装のデフォルト部分を提供できます。ただし、これは一般的な手法ではありません。
クォーク

9

virtualキーワードはどのように機能しますか?

人間が基本階級であると仮定すると、インド人は人間から派生しています。

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()は、インドのオブジェクトを指す場合でも呼び出されます。

私は、トップ投票の答えは誤解を招くものだと思います-virtualが派生クラスでオーバーライドされた実装を持つことができるかどうかにかかわらず、すべてのメソッド。C ++を特に参照すると、正しい違いは、関連する関数のバインディング(実行時(仮想が使用されている場合))バインディングとコンパイル時(仮想が使用されていないがメソッドがオーバーライドされ、ベースポインターが派生オブジェクトにポイントされている場合)バインディングです。

別の誤解を招くコメントがあるようです、

「ジャスティン、「純粋な仮想」は、「この関数は基本クラスでは実装できない」という意味で使用される用語(キーワードではなく、以下の私の回答を参照)にすぎません。

これは間違っています!純粋な仮想関数は、ボディを持つこともでき、実装することもできます。真実は、抽象クラスの純粋な仮想関数を静的に呼び出すことができるということです!2人の非常に優れた著者は、Bjarne StroustrupとStan Lippman ...です。彼らが言語を書いたからです。


2
残念ながら、回答が賛成投票を開始すると、他のすべては無視されます。でも、彼らはより良いかもしれません。
LtWorf 2014年

3

仮想関数は、基本クラスで宣言され、派生クラスによって再定義されるメンバー関数です。仮想関数は継承順に階層化されています。派生クラスが仮想関数をオーバーライドしない場合、その基本クラス内で定義された関数が使用されます。

純粋仮想関数は、基本クラスに関連する定義を含まない関数です。 基本クラスには実装がありません。派生クラスは、この関数をオーバーライドする必要があります。


2

Simula、C ++、およびC#は、デフォルトで静的メソッドバインディングを使用します。プログラマーは、特定のメソッドに仮想バインディングを付けることにより、動的バインディングを使用するように指定できます。動的メソッドバインディングは、オブジェクト指向プログラミングの中心です。

オブジェクト指向プログラミングには、カプセル化、継承、動的メソッドバインディングという3つの基本概念が必要です。

カプセル化により、抽象化の実装の詳細を単純なインターフェースの背後に隠すことができます。

継承によって、新しい抽象化を既存の抽象化の拡張または改良として定義し、その特性の一部またはすべてを自動的に取得できます。

動的メソッドバインディングにより、古い抽象化が必要なコンテキストで使用されている場合でも、新しい抽象化が新しい動作を表示できます。


1

仮想メソッドは、派生クラスによってオーバーライドできますが、基本クラス(オーバーライドされるもの)での実装が必要です。

純粋仮想メソッドには、基本クラスの実装はありません。それらは、派生クラスによって定義される必要があります。(つまり、オーバーライドするものがないため、技術的にオーバーライドすることは適切な用語ではありません)。

派生クラスが基本クラスのメソッドをオーバーライドする場合、VirtualはデフォルトのJava動作に対応します。

純粋仮想メソッドは、抽象クラス内の抽象メソッドの動作に対応しています。また、純粋な仮想メソッドと定数のみを含むクラスは、インターフェイスのcppペンダントになります。


0

純粋仮想関数

このコードを試してください

#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関数は基本クラスで仮想としてマークされています。派生クラスで関数を検索して関数を実装しようとするコンパイラーを言います。見つからない場合は、基本関数を実行します。


はは、ここで何が問題なのかを理解するのに30秒かかりました... HelloW :)
hans

0

「仮想関数または仮想メソッドは、同じシグネチャを持つ関数によって、継承するクラス内でその動作をオーバーライドできる関数またはメソッドです」-ウィキペディア

これは仮想関数の良い説明ではありません。メンバーが仮想でなくても、継承するクラスがそれをオーバーライドできるからです。自分で試してみることができます。

この違いは、関数が基本クラスをパラメーターとして取るときに現れます。継承クラスを入力として指定すると、その関数はオーバーライドされた関数の基本クラス実装を使用します。ただし、その関数が仮想の場合、派生クラスで実装されている関数が使用されます。


0
  • 仮想関数には基本クラスと派生クラスの定義が必要ですが、必須ではありません。たとえば、ToString()またはtoString()関数は仮想であるため、ユーザー定義クラスでオーバーライドすることで独自の実装を提供できます。

  • 仮想関数は、通常のクラスで宣言および定義されます。

  • 純粋仮想関数は「= 0」で終わるように宣言する必要があり、抽象クラスでのみ宣言できます。

  • 純粋な仮想関数を持つ抽象クラスは、その純粋な仮想関数の定義を持つことができないため、その抽象クラスから派生したクラスで実装を提供する必要があることを意味します。


@rashedcsと同じメモ:確かに純粋仮想関数はその定義を持つことができます...
Jarek C
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.