web-dev-qa-db-ja.com

C ++仮想/純仮想の説明

関数が仮想関数として定義されていて、純粋仮想関数と同じであるとしたら、それは厳密にはどういう意味ですか?

316
Justin

から ウィキペディアの仮想関数 ...

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

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

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

一方….

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

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

バーチャルは基本クラスの機能をオーバーライドする方法を提供し、純粋バーチャルはrequire itです。

310
Diego Dias

ここで何度か繰り返したように、私はウィキペディアの仮想の定義についてコメントしたいと思います。 [この答えが書かれた時点で]ウィキペディアは、仮想メソッドをサブクラスでオーバーライドできるものとして定義しました。 [幸いなことに、ウィキペディアはそれ以来編集されてきました、そしてそれは今これを正しく説明しています。]それは間違っています。仮想的なことは、多態性、つまり実行時に最も派生するメソッドのオーバーライドを選択する能力を与えることです。

次のコードを見てください。

#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.

派生はBaseのすべてのメソッドをオーバーライドします。仮想メソッドだけでなく、非仮想メソッドもオーバーライドします。

Base-to-Derived(bDerived)がある場合、NonVirtualを呼び出すとBaseクラスの実装が呼び出されることがわかります。これはコンパイル時に解決されます。コンパイラは、bDerivedがBase *であり、NonVirtualが仮想ではないと判断したため、Baseクラスの解決を行います。

ただし、Virtualを呼び出すと、派生クラスの実装が呼び出されます。キーワードvirtualのため、メソッドの選択はコンパイル時ではなく、実行時に行われます。ここでコンパイル時に起こることは、コンパイラがこれがBase *であり、仮想メソッドを呼び出していることを認識しているため、Baseクラスの代わりにvtableへの呼び出しを挿入することです。このvtableは実行時にインスタンス化されるため、最も派生するオーバーライドに対する実行時の解決策となります。

これが混乱しすぎないことを願います。つまり、どのメソッドでもオーバーライドできますが、ポリモーフィズム、つまり最も派生したオーバーライドの実行時選択を可能にするのは仮想メソッドのみです。しかし実際には、非仮想メソッドをオーバーライドすることは悪い習慣と見なされ、使用されることはめったにありません。

193
Asik

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派生クラスを自動的にAnimalにキャストします。

これを行うと:

Duck d;
SomeFunction(&d);

それは '2'を出力します。これを行うと:

Horse h;
SomeFunction(&h);

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

Animal a;
SomeFunction(&a);

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

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

a)抽象クラス

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

b)インタフェース

これらはすべての関数が純粋に仮想である「空の」クラスなので、すべての関数を派生させてから実装する必要があります。

111
JBRWilkinson

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

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

30
Nick Haddad

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

@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()
15
John Millikin

仮想キーワードはどのように機能しますか。

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が使用されていないがメソッドがオーバーライドされ、ベースポインタが派生オブジェクトを指す場合)のバインディングです。

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

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

このIS間違っています。純粋に仮想的な機能も本体を持つことができ、実装することができます!実のところ、抽象クラスの純粋仮想関数は静的に呼び出すことができます。 2人の非常に良い作家はBjarne StroustrupとStan Lippmanです....彼らが言語を書いたので。

9
McMurdo

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

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

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

Inheritanceは、新しい抽象化を既存の抽象化の拡張または改良として定義し、その特性の一部または全部を自動的に取得することを可能にします。

動的メソッドバインディングは、古い抽象を期待する文脈で使用された場合でも、新しい抽象がその新しい振る舞いを表示することを可能にします。

2
PJT

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

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

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

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

1
johannes_lalala

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

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

0
rashedcs

純粋仮想機能

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

#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();
}

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

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

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

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

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

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

  • 純粋仮想関数は "= 0"で終わるように宣言しなければならず、抽象クラス内でしか宣言できません。

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

0
Sohail xIN3N