web-dev-qa-db-ja.com

なぜC++の仮想関数が必要なのですか?

私はC++を学んでいます、そして私はただ仮想関数に入っています。

私が読んだことから(本とオンラインで)、仮想関数は派生クラスでオーバーライドできる基本クラスの関数です。

しかし本の前半で、基本継承について学ぶとき、私はvirtualを使わずに派生クラスの基底関数をオーバーライドすることができました。

だから私はここで何が足りないのですか?私は仮想機能にもっとたくさんあることを知っています、そしてそれは重要であるように私はそれが正確に何であるかについて明確にしたいです。オンラインで正解は見つかりません。

1119
Jake Wilson

これが virtual 関数が何であるかだけではなく、なぜそれらが必要なのかを理解する方法です。

これら2つのクラスがあるとしましょう。

class Animal
{
    public:
        void eat() { std::cout << "I'm eating generic food."; }
};

class Cat : public Animal
{
    public:
        void eat() { std::cout << "I'm eating a rat."; }
};

あなたの主な機能では:

Animal *animal = new Animal;
Cat *cat = new Cat;

animal->eat(); // Outputs: "I'm eating generic food."
cat->eat();    // Outputs: "I'm eating a rat."

これまでのところ、とても良いですね。動物は一般的な食べ物を食べ、猫はラットを食べ、すべてvirtualなしで。

ちょっと変更して、eat()が中間関数(この例では単純な関数)を介して呼び出されるようにしましょう。

// This can go at the top of the main.cpp file
void func(Animal *xyz) { xyz->eat(); }

今私たちの主な機能は次のとおりです。

Animal *animal = new Animal;
Cat *cat = new Cat;

func(animal); // Outputs: "I'm eating generic food."
func(cat);    // Outputs: "I'm eating generic food."

うーん...私たちは猫をfunc()に渡しました、しかしそれはネズミを食べません。 func()をオーバーロードしてCat*を取得する必要がありますか?あなたがAnimalからより多くの動物を派生させなければならないならば、それらはすべて彼ら自身のfunc()を必要とします。

解決策は、Animalクラスのeat()を仮想関数にすることです。

class Animal
{
    public:
        virtual void eat() { std::cout << "I'm eating generic food."; }
};

class Cat : public Animal
{
    public:
        void eat() { std::cout << "I'm eating a rat."; }
};

メイン:

func(animal); // Outputs: "I'm eating generic food."
func(cat);    // Outputs: "I'm eating a rat."

完了しました。

2492
M Perry

「バーチャル」がなければ、「アーリーバインディング」になります。メソッドのどの実装が使用されるかは、呼び出したポインタの型に基づいてコンパイル時に決定されます。

「仮想」を使用すると、「遅延バインディング」を取得します。メソッドのどの実装が使用されるかは、ポイントされたオブジェクトの型に基づいて実行時に決まります。これは必ずしも、そのオブジェクトを指すポインタの種類に基づいて考えるとは限りません。

class Base
{
  public:
            void Method1 ()  {  std::cout << "Base::Method1" << std::endl;  }
    virtual void Method2 ()  {  std::cout << "Base::Method2" << std::endl;  }
};

class Derived : public Base
{
  public:
    void Method1 ()  {  std::cout << "Derived::Method1" << std::endl;  }
    void Method2 ()  {  std::cout << "Derived::Method2" << std::endl;  }
};

Base* obj = new Derived ();
  //  Note - constructed as Derived, but pointer stored as Base*

obj->Method1 ();  //  Prints "Base::Method1"
obj->Method2 ();  //  Prints "Derived::Method2"

_ edit _ - この質問 を参照。

また、 - このチュートリアル では、C++での初期バインディングと遅延バインディングについて説明します。

598
Steve314

それを実証するには、最低1レベルの継承とダウンキャストが必要です。これは非常に簡単な例です。

class Animal
{        
    public: 
      // turn the following virtual modifier on/off to see what happens
      //virtual   
      std::string Says() { return "?"; }  
};

class Dog: public Animal
{
    public: std::string Says() { return "Woof"; }
};

void test()
{
    Dog* d = new Dog();
    Animal* a = d;       // refer to Dog instance with Animal pointer

    cout << d->Says();   // always Woof
    cout << a->Says();   // Woof or ?, depends on virtual
}
79
Henk Holterman

safe downcastingsimplicityおよびconcisenessの仮想メソッドが必要です。

それが仮想メソッドの役割です。見かけはシンプルで簡潔なコードで安全にダウンキャストし、そうでなければより複雑で冗長なコードでの安全でない手動キャストを回避します。


非仮想メソッド⇒静的バインディング

次のコードは意図的に「間違っています」。 valueメソッドをvirtualとして宣言していないため、意図しない「間違った」結果、つまり0が生成されます。

#include <iostream>
using namespace std;

class Expression
{
public:
    auto value() const
        -> double
    { return 0.0; }         // This should never be invoked, really.
};

class Number
    : public Expression
{
private:
    double  number_;

public:
    auto value() const
        -> double
    { return number_; }     // This is OK.

    Number( double const number )
        : Expression()
        , number_( number )
    {}
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

public:
    auto value() const
        -> double
    { return a_->value() + b_->value(); }       // Uhm, bad! Very bad!

    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    {}
};

auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

「悪い」とコメントされた行では、Expression::valueメソッドが呼び出されます。これは、静的に既知のタイプ(コンパイル時に既知のタイプ)がExpressionであり、valueメソッドが仮想ではないためです。


仮想メソッド⇒動的バインディング。

valueを静的に既知のタイプvirtualExpressionとして宣言すると、各呼び出しはこれが実際のオブジェクトのタイプを確認し、そのためのvalueの関連する実装を呼び出します動的タイプ

#include <iostream>
using namespace std;

class Expression
{
public:
    virtual
    auto value() const -> double
        = 0;
};

class Number
    : public Expression
{
private:
    double  number_;

public:
    auto value() const -> double
        override
    { return number_; }

    Number( double const number )
        : Expression()
        , number_( number )
    {}
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

public:
    auto value() const -> double
        override
    { return a_->value() + b_->value(); }    // Dynamic binding, OK!

    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    {}
};

auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

仮想メソッドはvirtualizedと呼ばれるであるため、ここでの出力は本来あるべき__ 6.86です。これは、呼び出しの動的バインディングとも呼ばれます。少しのチェックが実行され、オブジェクトの実際の動的タイプが検出され、その動的タイプに関連するメソッド実装が呼び出されます。

関連する実装は、最も具体的な(最も派生した)クラスの実装です。

ここでの派生クラスのメソッド実装にはvirtualのマークは付けられませんが、代わりにoverrideのマークが付けられます。 virtualとマークすることもできますが、自動的に仮想になります。 overrideキーワードは、ある基本クラスにそのような仮想メソッドがnotある場合、エラーが発生することを保証します(これは望ましいことです)。


仮想メソッドなしでこれを行うことのさ

virtualがなければ、動的バインディングのいくつかのDo It Yourselfバージョンを実装する必要があります。これは一般に、安全でない手動のダウンキャスト、複雑さ、および冗長性を伴います。

単一の関数の場合、ここにあるように、オブジェクトに関数ポインターを保存し、その関数ポインターを介して呼び出すだけで十分ですが、それでもなお、いくつかの安全でないダウンキャスト、複雑さ、冗長性が伴います。

#include <iostream>
using namespace std;

class Expression
{
protected:
    typedef auto Value_func( Expression const* ) -> double;

    Value_func* value_func_;

public:
    auto value() const
        -> double
    { return value_func_( this ); }

    Expression(): value_func_( nullptr ) {}     // Like a pure virtual.
};

class Number
    : public Expression
{
private:
    double  number_;

    static
    auto specific_value_func( Expression const* expr )
        -> double
    { return static_cast<Number const*>( expr )->number_; }

public:
    Number( double const number )
        : Expression()
        , number_( number )
    { value_func_ = &Number::specific_value_func; }
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

    static
    auto specific_value_func( Expression const* expr )
        -> double
    {
        auto const p_self  = static_cast<Sum const*>( expr );
        return p_self->a_->value() + p_self->b_->value();
    }

public:
    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    { value_func_ = &Sum::specific_value_func; }
};


auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

これを確認する1つの肯定的な方法は、上記のように安全でないダウンキャスティング、複雑さ、および冗長性に遭遇した場合、多くの場合、1つまたは複数の仮想メソッドが本当に役立ちます。

41

仮想関数はRuntime Polymorphismをサポートするために使用されます。

つまり、virtual keywordはコンパイラコンパイル時に(関数バインディングの)決定をしないように、実行時は延期しない " _に指示します。

  • 基底クラス宣言でキーワードvirtualを前に付けることで、関数を仮想化することができます。例えば、

     class Base
     {
        virtual void func();
     }
    
  • 基本クラスが仮想メンバ関数を持つ場合、基本クラスから継承するクラスはすべて{再定義厳密に同じプロトタイプを持つことができます。関数のインターフェースではなく、再定義されています。

     class Derive : public Base
     {
        void func();
     }
    
  • 基本クラスポインタは、派生クラスオブジェクトだけでなく基本クラスオブジェクトも指すために使用できます。

  • Baseクラスポインタを使用して仮想関数が呼び出されると、コンパイラは実行時にどのバージョンの関数(すなわち、Baseクラスバージョンまたはオーバーライドされた派生クラスバージョン)を呼び出すかを決定する。これはRuntime Polymorphismと呼ばれます。
35
user6359267

基本クラスがBaseで、派生クラスがDerの場合、実際にDerのインスタンスを指すBase *pポインタを使用できます。 p->foo();を呼び出すとき、foonot virtualの場合、Baseのバージョンは、pが実際にDerを指しているという事実を無視して実行されます。 foo is virtualの場合、p->foo()は、指し示されている項目の実際のクラスを完全に考慮に入れて、fooの「最下位」のオーバーライドを実行します。したがって、仮想と非仮想の違いは実際には非常に重要です。前者はOOプログラミングの中心的概念であるランタイム ポリモーフィズム を許可しますが、後者は許可しません。

32
Alex Martelli

仮想機能の必要性を説明した[わかりやすい]

#include<iostream>

using namespace std;

class A{
public: 
        void show(){
        cout << " Hello from Class A";
    }
};

class B :public A{
public:
     void show(){
        cout << " Hello from Class B";
    }
};


int main(){

    A *a1 = new B; // Create a base class pointer and assign address of derived object.
    a1->show();

}

出力は以下のようになります。

Hello from Class A.

しかし、仮想機能付き:

#include<iostream>

using namespace std;

class A{
public:
    virtual void show(){
        cout << " Hello from Class A";
    }
};

class B :public A{
public:
    virtual void show(){
        cout << " Hello from Class B";
    }
};


int main(){

    A *a1 = new B;
    a1->show();

}

出力は以下のようになります。

Hello from Class B.

したがって、仮想関数を使用すると、ランタイム多態性を実現できます。

26
Ajay GU

オーバーライドとオーバーロードを区別する必要があります。 virtualキーワードがないと、基本クラスのメソッドをオーバーロードするだけです。これは隠れることを意味します。基本クラスBaseと派生クラスSpecializedがあり、どちらもvoid foo()を実装しているとしましょう。これでBaseのインスタンスを指すSpecializedへのポインタができました。 foo()を呼び出すと、virtualの違いがわかります。メソッドが仮想の場合はSpecializedの実装が使用され、欠けている場合はBaseのバージョンが選択されます。基本クラスからメソッドをオーバーロードしないことがベストプラクティスです。メソッドを非仮想化することは、サブクラスでのその拡張は意図されていないことをあなたに言うその作者の方法です。

22
h0b0

上記の答えと同じ概念を使用していますが、私は仮想機能の別の使用法を追加したいと思いますが、私はその言及に値すると思います。

バーチャルデストラクタ

基本クラスのデストラクタを仮想として宣言せずに、以下のこのプログラムを検討してください。 Cat用のメモリがクリーンアップされていない可能性があります。

class Animal {
    public:
    ~Animal() {
        cout << "Deleting an Animal" << endl;
    }
};
class Cat:public Animal {
    public:
    ~Cat() {
        cout << "Deleting an Animal name Cat" << endl;
    }
};

int main() {
    Animal *a = new Cat();
    delete a;
    return 0;
}

出力:

Deleting an Animal
class Animal {
    public:
    virtual ~Animal() {
        cout << "Deleting an Animal" << endl;
    }
};
class Cat:public Animal {
    public:
    ~Cat(){
        cout << "Deleting an Animal name Cat" << endl;
    }
};

int main() {
    Animal *a = new Cat();
    delete a;
    return 0;
}

出力:

Deleting an Animal name Cat
Deleting an Animal
21
Aryaman Gupta

なぜC++の仮想メソッドが必要なのですか?

素早い回答:

  1. それは私たちに必要な「食材」の一つを提供します1 オブジェクト指向プログラミングのための

Bjarne Stroustrup C++プログラミングの原則と実践、(14.3):

仮想関数は、基本クラスで関数を定義し、ユーザーが基本クラス関数を呼び出すときに呼び出される派生クラスで同じ名前と型の関数を持つことができます。呼び出される関数は、使用されるオブジェクトのタイプに基づいて実行時に決定されるため、これはしばしば ランタイム多型 / 動的ディスパッチ または ランタイムディスパッチ と呼ばれます。

  1. 仮想関数呼び出しが必要な場合は、最も効率的な実装です。  2

バーチャルコールを処理するには、 派生オブジェクトに関連する1つ以上のデータが必要です  3。通常行われる方法は、関数テーブルのアドレスを追加することです。このテーブルは通常 仮想テーブル または 仮想関数テーブル と呼ばれ、そのアドレスはしばしば 仮想ポインタ と呼ばれます。各仮想機能は仮想テーブルのスロットを取得します。呼び出し元のオブジェクト(派生)の種類に応じて、仮想関数は順番にそれぞれのオーバーライドを呼び出します。


1.継承、ランタイムポリモーフィズム、およびカプセル化の使用は、 オブジェクト指向プログラミング の最も一般的な定義です。

2.他の言語機能を使用して実行時に選択肢を選択することで、機能をそれほど速くすることも、使用するメモリを少なくすることもできません。 Bjarne Stroustrup C++プログラミング:原理と実践(14.3.1)

3.仮想関数を含む基本クラスを呼び出すときに、どの関数が実際に呼び出されるのかを伝えるための何か。

18
Ziezi

基本クラスに関数がある場合は、派生クラスでそれをRedefineまたはOverrideにすることができます。

メソッドの再定義 :基本クラスのメソッドの新しい実装は派生クラスで与えられています。 しません Dynamic bindingを容易にします。

メソッドをオーバーライドする Redefining a派生クラスの基本クラスのvirtual method仮想メソッド はダイナミックバインディングを容易にする

だからあなたが言ったとき:

しかし本の前半で、基本継承について学ぶとき、 'virtual'を使わずに派生クラスの基本メソッドをオーバーライドすることができました。

基本クラスのメソッドが仮想ではなかったため、オーバーライドしていなかったのではなく、再定義しました。

14
nitin_cherian

私は会話の形で私の答えを読むことをお勧めします。


なぜ仮想関数が必要なのか

多型のせいで。

多態性とは

ベースポインタが派生型オブジェクトも指すことができるという事実。

この多態性の定義は、仮想関数の必要性にどのようにつながるのでしょうか。

さて、アーリーバインディングを通して。

アーリーバインディングとは何ですか?

C++のアーリーバインディング(コンパイル時バインディング)は、プログラムが実行される前に関数呼び出しが修正されることを意味します。

そう……?

そのため、関数のパラメータとして基本型を使用すると、コンパイラは基本インタフェースのみを認識し、派生クラスからの引数を指定してその関数を呼び出すと、スライスされてしまいます。

それが私たちのやりたいことではないのなら、なぜこれが許可されるのでしょうか。

多態性が必要だから!

それでは、多態性の利点は何ですか?

基本型ポインタを単一の関数のパラメータとして使用すると、プログラムの実行時に、その単一の間接参照を使用して、問題なく各派生型インタフェース(そのメンバ関数など)にアクセスできます。ベースポインタ.

どんな仮想関数が良いのかまだわかりません...!そしてこれは私の最初の質問でした!

まあ、これはあなたがあまりにも早くあなたの質問をしたからです!

なぜ仮想関数が必要なのか

ベースポインタを使用して関数を呼び出したとします。ベースポインタは、その派生クラスの1つからのオブジェクトのアドレスを持っています。上で説明したように、実行時にはこのポインタは間接参照されますが、これまでのところ非常に優れていますが、「派生クラスからの」メソッド(==メンバー関数)が実行されることを期待します。しかし、同じメソッド(同じヘッダを持つもの)がすでに基底クラスに定義されているので、なぜあなたのプログラムは他のメソッドを選択したくないのでしょうか。言い換えれば、私たちがこれまで私たちがこれまでに通常起こっていたことから、このシナリオをどのように見分けることができるのでしょう。

簡単な答えは「基底の仮想メンバー関数」です、そしてもう少し長い答えは、プログラムが基底クラスの仮想関数を見れば、あなたが使おうとしていることを知っている(実現している)ということです。 "多態性"などの派生クラス( v-table 、遅延バインディングの形式)を使用して、同じヘッダーを持つ別のメソッドを見つけることができますが、予想通りに実装が異なります。

なぜ違う実装なのか

ナックルヘッド! いい本を読みに行く

さて、ちょっと待って、ちょっと派生型のポインタを使うことができるのに、どうしてベースポインタを使わないのでしょうか。あなたは裁判官になる、このすべての頭痛はそれだけの価値がありますか?これら2つの断片を見てください。

// 1:

Parent* p1 = &boy;
p1 -> task();
Parent* p2 = &girl;
p2 -> task();

// 2:

Boy* p1 = &boy;
p1 -> task();
Girl* p2 = &girl;
p2 -> task();

さて、私は 1 2 よりもまだ優れていると思いますが、 1 のように書くこともできます。

// 1:

Parent* p1 = &boy;
p1 -> task();
p1 = &girl;
p1 -> task();

さらに、これはまだ私がこれまでに説明したすべてのことを意図的に使用したものではないことに注意する必要があります。これの代わりに、例えば、それぞれの派生クラスからのメソッドをそれぞれ使用するプログラム内の関数があった状況を想定してください(getMonthBenefit())。

double totalMonthBenefit = 0;    
std::vector<CentralShop*> mainShop = { &shop1, &shop2, &shop3, &shop4, &shop5, &shop6};
for(CentralShop* x : mainShop){
     totalMonthBenefit += x -> getMonthBenefit();
}

さて、これを書き直すことを試みなさい、あらゆる頭痛なしで!

double totalMonthBenefit=0;
Shop1* branch1 = &shop1;
Shop2* branch2 = &shop2;
Shop3* branch3 = &shop3;
Shop4* branch4 = &shop4;
Shop5* branch5 = &shop5;
Shop6* branch6 = &shop6;
totalMonthBenefit += branch1 -> getMonthBenefit();
totalMonthBenefit += branch2 -> getMonthBenefit();
totalMonthBenefit += branch3 -> getMonthBenefit();
totalMonthBenefit += branch4 -> getMonthBenefit();
totalMonthBenefit += branch5 -> getMonthBenefit();
totalMonthBenefit += branch6 -> getMonthBenefit();

そして実際には、これはまだ人為的な例かもしれません!

14
M-J

あなたが根本的なメカニズムを知っているなら、それは役立ちます。 C++はCプログラマーによって使用されるいくつかのコーディング技法を形式化します。「クラス」は「オーバーレイ」を使用して置き換えられます - 共通のヘッダーセクションを持つ構造体は異なるタイプのオブジェクトを処理するために使用されます。通常、オーバーレイのベース構造体(共通部分)には、各オブジェクトタイプの異なるルーチンセットを指す関数テーブルへのポインタがあります。 C++も同じことをしますが、メカニズムは隠します。つまり、Cが(*ptr->func_table[func_num])(ptr,...)であるようにfuncが仮想であるC++ ptr->func(...)では、派生クラス間で変更されるのはfunc_tableの内容です。 [非仮想メソッドptr-> func()は単にmangled_func(ptr、..)に変換される。]

つまり、派生クラスのメソッドを呼び出すには基本クラスを理解するだけで済みます。つまり、ルーチンがクラスAを理解している場合は、派生クラスBのポインタを渡すと、仮想メソッドは次のようになります。あなたが関数テーブルBを通過するので、AではなくBのBが指す。

11
Kev

キーワードvirtualは、早期バインディングを実行しないようにコンパイラーに指示します。代わりに、遅延バインディングを実行するために必要なすべてのメカニズムを自動的にインストールします。これを達成するために、典型的なコンパイラ1は、仮想機能を含む各クラスに対して単一のテーブル(VTABLEと呼ばれる)を作成する。コンパイラは、その特定のクラスに対する仮想機能のアドレスをVTABLEに入れる。仮想関数を持つ各クラスには、そのオブジェクトのVTABLEを指すvpointer(略してVPTR)と呼ばれるポインタを密かに置きます。基底クラスポインタを介して仮想関数呼び出しを行うと、コンパイラは静かにVPTRを取得してVTABLE内の関数アドレスを検索するコードを挿入し、正しい関数を呼び出して遅延バインディングを実行させます。

このリンクの詳細/ http://cplusplusinterviews.blogspot.sg/2015/04/virtual-mechanism.html

9
rvkreddy

virtual キーワードは、 ポインタの クラスではなく オブジェクトの クラスで定義されたメソッド実装をコンパイラに選択させる。

Shape *shape = new Triangle(); 
cout << shape->getName();

上記の例では、基本クラスShapeでgetName()がvirtualとして定義されていない限り、Shape :: getNameがデフォルトで呼び出されます。これにより、コンパイラはShapeクラスではなくTriangleクラスでgetName()実装を探すようになります。

仮想テーブル は、コンパイラがサブクラスのさまざまな仮想メソッド実装を追跡するメカニズムです。これは動的ディスパッチとも呼ばれ、isそれに関連したオーバーヘッドがあります)。

最後に、なぜC++ではvirtualが必要なのか、Javaのようにデフォルトの動作にしないのか。

  1. C + +は "ゼロオーバーヘッド"と "あなたが使うもののために支払う"の原則に基づいています。それで、あなたがそれを必要としない限り、それはあなたのために動的なディスパッチを実行しようとしません。
  2. インタフェースをより詳細に制御するため。関数を非仮想にすることで、interface/abstractクラスはそのすべての実装における動作を制御できます。
6
javaProgrammer

なぜ仮想関数が必要なのですか?

仮想関数は不要な型キャスト問題を回避し、派生クラスポインタを使用して派生クラス固有の関数を呼び出すことができるのに、なぜ仮想関数が必要なのかを議論する人もいます!答えは - 大規模システムにおける継承の概念全体を無効にする単一ポインタベースクラスオブジェクトを持つことが非常に望ましい開発。

仮想関数の重要性を理解するために、以下の2つの単純なプログラムを比較しましょう。

仮想機能なしのプログラム

#include <iostream>
using namespace std;

class father
{
    public: void get_age() {cout << "Fathers age is 50 years" << endl;}
};

class son: public father
{
    public : void get_age() { cout << "son`s age is 26 years" << endl;}
};

int main(){
    father *p_father = new father;
    son *p_son = new son;

    p_father->get_age();
    p_father = p_son;
    p_father->get_age();
    p_son->get_age();
    return 0;
}

出力:

Fathers age is 50 years
Fathers age is 50 years
son`s age is 26 years

仮想機能付きプログラム:

#include <iostream>
using namespace std;

class father
{
    public:
        virtual void get_age() {cout << "Fathers age is 50 years" << endl;}
};

class son: public father
{
    public : void get_age() { cout << "son`s age is 26 years" << endl;}
};

int main(){
    father *p_father = new father;
    son *p_son = new son;

    p_father->get_age();
    p_father = p_son;
    p_father->get_age();
    p_son->get_age();
    return 0;
}

出力:

Fathers age is 50 years
son`s age is 26 years
son`s age is 26 years

両方の出力を綿密に分析することによって、仮想機能の重要性を理解することができます。

4
akshaypmurgod

OOP回答:サブタイプ多型

C++では、ウィキペディアから定義を適用する場合、ポリモーフィズム、より正確にはサブタイピングまたはサブタイプポリモーフィズムを実現するための仮想メソッドが必要です。

ウィキペディア、サブタイピング、2019-01-09:プログラミング言語理論では、サブタイピング(サブタイプポリモーフィズムまたは包含ポリモーフィズム)は、サブタイプが別のデータタイプ(スーパータイプ)に何らかの概念で関連付けられているデータタイプであるタイプポリモーフィズムの形式ですスーパータイプの要素を操作すると書かれたプログラム要素、通常サブルーチンまたは関数は、サブタイプの要素でも操作できることを意味します。

注:サブタイプは基本クラスを意味し、サブタイプは継承されたクラスを意味します。

サブタイプ多型に関するさらなる参考文献

技術的な回答:Dynamic Dispatch

基本クラスへのポインターがある場合、メソッドの呼び出し(仮想として宣言されている)は、作成されたオブジェクトの実際のクラスのメソッドにディスパッチされます。これは、C++でサブタイプ多態性を実現する方法です。

さらに読むC++の多態性とDynamic Dispatch

実装の答え:vtableエントリを作成します

メソッドの「仮想」修飾子ごとに、C++コンパイラは通常、メソッドが宣言されているクラスのvtableにエントリを作成します。これは、一般的なC++コンパイラがDynamic Dispatchを実現する方法です。

さらに読むvtable


コード例

#include <iostream>

using namespace std;

class Animal {
public:
    virtual void MakeTypicalNoise() = 0; // no implementation needed, for abstract classes
    virtual ~Animal(){};
};

class Cat : public Animal {
public:
    virtual void MakeTypicalNoise()
    {
        cout << "Meow!" << endl;
    }
};

class Dog : public Animal {
public:
    virtual void MakeTypicalNoise() { // needs to be virtual, if subtype polymorphism is also needed for Dogs
        cout << "Woof!" << endl;
    }
};

class Doberman : public Dog {
public:
    virtual void MakeTypicalNoise() {
        cout << "Woo, woo, woow!";
        cout << " ... ";
        Dog::MakeTypicalNoise();
    }
};

int main() {

    Animal* apObject[] = { new Cat(), new Dog(), new Doberman() };

    const   int cnAnimals = sizeof(apObject)/sizeof(Animal*);
    for ( int i = 0; i < cnAnimals; i++ ) {
        apObject[i]->MakeTypicalNoise();
    }
    for ( int i = 0; i < cnAnimals; i++ ) {
        delete apObject[i];
    }
    return 0;
}

サンプルコードの出力

Meow!
Woof!
Woo, woo, woow! ... Woof!

コード例のUMLクラス図

UML class diagram of code example

効率性については、 仮想関数 は早期束縛関数としてはやや非効率的です。

「この仮想呼び出しメカニズムは、「通常の関数呼び出し」メカニズムとほぼ同じくらい効率的に(25%以内)にすることができます。そのスペースオーバーヘッドは、仮想関数を含むクラスの各オブジェクト内の1ポインター+各クラスごとに1 vtblです」[ C++ツアー Bjarne Stroustrupによる]

2
Duke

これが、仮想メソッドが使用される理由を説明する完全な例です。

#include <iostream>

using namespace std;

class Basic
{
    public:
    virtual void Test1()
    {
        cout << "Test1 from Basic." << endl;
    }
    virtual ~Basic(){};
};
class VariantA : public Basic
{
    public:
    void Test1()
    {
        cout << "Test1 from VariantA." << endl;
    }
};
class VariantB : public Basic
{
    public:
    void Test1()
    {
        cout << "Test1 from VariantB." << endl;
    }
};

int main()
{
    Basic *object;
    VariantA *vobjectA = new VariantA();
    VariantB *vobjectB = new VariantB();

    object=(Basic *) vobjectA;
    object->Test1();

    object=(Basic *) vobjectB;
    object->Test1();

    delete vobjectA;
    delete vobjectB;
    return 0;
}
2
user3371350

仮想メソッドはインターフェース設計で使用されます。たとえばWindowsには、以下のようにIUnknownという名前のインターフェイスがあります。

interface IUnknown {
  virtual HRESULT QueryInterface (REFIID riid, void **ppvObject) = 0;
  virtual ULONG   AddRef () = 0;
  virtual ULONG   Release () = 0;
};

これらのメソッドは、実装するのがインタフェースユーザに任されています。それらは、IUnknownを継承しなければならない特定のオブジェクトの作成と破壊に不可欠です。この場合、ランタイムは3つのメソッドを認識しており、それらを呼び出すときにそれらが実装されることを期待しています。したがって、ある意味では、それらはオブジェクト自体とそのオブジェクトを使用するものとの間の契約として機能します。

2
user2074102

メソッドが仮想的に宣言されたら、オーバーライドに 'virtual'キーワードを使用する必要はないという事実に言及していると思います。

class Base { virtual void foo(); };

class Derived : Base 
{ 
  void foo(); // this is overriding Base::foo
};

Baseのfoo宣言で 'virtual'を使用していなければ、Derivedのfooはそれを隠しているだけです。

1
edwinc

これが最初の2つの答えに対するC++コードのマージ版です。

#include        <iostream>
#include        <string>

using   namespace       std;

class   Animal
{
        public:
#ifdef  VIRTUAL
                virtual string  says()  {       return  "??";   }
#else
                string  says()  {       return  "??";   }
#endif
};

class   Dog:    public Animal
{
        public:
                string  says()  {       return  "woof"; }
};

string  func(Animal *a)
{
        return  a->says();
}

int     main()
{
        Animal  *a = new Animal();
        Dog     *d = new Dog();
        Animal  *ad = d;

        cout << "Animal a says\t\t" << a->says() << endl;
        cout << "Dog d says\t\t" << d->says() << endl;
        cout << "Animal dog ad says\t" << ad->says() << endl;

        cout << "func(a) :\t\t" <<      func(a) <<      endl;
        cout << "func(d) :\t\t" <<      func(d) <<      endl;
        cout << "func(ad):\t\t" <<      func(ad)<<      endl;
}

2つの異なる結果があります。

#define virtual がないと、コンパイル時にバインドされます。 Animal * adとfunc(Animal *)はすべてAnimal's says()メソッドを指しています。

$ g++ virtual.cpp -o virtual
$ ./virtual 
Animal a says       ??
Dog d says      woof
Animal dog ad says  ??
func(a) :       ??
func(d) :       ??
func(ad):       ??

#define virtual を指定すると、実行時にバインドされます。 Dog * d、Animal * adおよびfunc(Animal *)は、Dogがそのオブジェクトタイプであるため、Dog's says()メソッドを参照します。 [Dog's says() "woof"]メソッドが定義されていないのでなければ、それはクラスツリーの最初に検索されたものになるでしょう。

$ g++ virtual.cpp -D VIRTUAL -o virtual
$ ./virtual 
Animal a says       ??
Dog d says      woof
Animal dog ad says  woof
func(a) :       ??
func(d) :       woof
func(ad):       woof

興味深いことに、 Pythonのすべてのクラス属性(データとメソッド)は事実上仮想 です。すべてのオブジェクトは実行時に動的に作成されるので、型宣言やキーワードvirtualの必要はありません。以下はPythonのバージョンのコードです。

class   Animal:
        def     says(self):
                return  "??"

class   Dog(Animal):
        def     says(self):
                return  "woof"

def     func(a):
        return  a.says()

if      __== "__main__":

        a = Animal()
        d = Dog()
        ad = d  #       dynamic typing by assignment

        print("Animal a says\t\t{}".format(a.says()))
        print("Dog d says\t\t{}".format(d.says()))
        print("Animal dog ad says\t{}".format(ad.says()))

        print("func(a) :\t\t{}".format(func(a)))
        print("func(d) :\t\t{}".format(func(d)))
        print("func(ad):\t\t{}".format(func(ad)))

出力は以下のとおりです。

Animal a says       ??
Dog d says      woof
Animal dog ad says  woof
func(a) :       ??
func(d) :       woof
func(ad):       woof

これはC++の仮想定義と同じです。dadは、同じDogインスタンスを参照/指している2つの異なるポインター変数です。式(ad is d)はTrueを返し、それらの値は同じ< main 。0xb79f72ccのDogオブジェクト>です。

1
Leon Chang

あなたは関数ポインタをよく知っていますか?仮想関数は、データを(クラスメンバとして)仮想関数に簡単にバインドできることを除いて、同様のアイデアです。データを関数ポインタにバインドするのはそれほど簡単ではありません。私にとって、これが主な概念上の違いです。ここにある他の多くの答えは、「なぜ...多態性だから」と言っているだけです。

0
user2445507

肝心なのは、仮想機能によって生活が楽になるということです。 M Perryのアイデアをいくつか使って、仮想関数がなくてメンバ関数ポインタしか使えないとしたらどうなるかを説明しましょう。仮想関数を使わない通常の推定では、

 class base {
 public:
 void helloWorld() { std::cout << "Hello World!"; }
  };

 class derived: public base {
 public:
 void helloWorld() { std::cout << "Greetings World!"; }
 };

 int main () {
      base hwOne;
      derived hwTwo = new derived();
      base->helloWorld(); //prints "Hello World!"
      derived->helloWorld(); //prints "Hello World!"

わかりました、それで私達が知っていることです。それでは、メンバ関数ポインタを使ってみましょう。

 #include <iostream>
 using namespace std;

 class base {
 public:
 void helloWorld() { std::cout << "Hello World!"; }
 };

 class derived : public base {
 public:
 void displayHWDerived(void(derived::*hwbase)()) { (this->*hwbase)(); }
 void(derived::*hwBase)();
 void helloWorld() { std::cout << "Greetings World!"; }
 };

 int main()
 {
 base* b = new base(); //Create base object
 b->helloWorld(); // Hello World!
 void(derived::*hwBase)() = &derived::helloWorld; //create derived member 
 function pointer to base function
 derived* d = new derived(); //Create derived object. 
 d->displayHWDerived(hwBase); //Greetings World!

 char ch;
 cin >> ch;
 }

メンバ関数ポインタを使ってできることはいくつかありますが、それらは仮想関数ほど柔軟ではありません。クラス内でメンバ関数ポインタを使用するのは難しいです。メンバー関数ポインタは、少なくとも私の実務では、常に上記の例のように、メイン関数内またはメンバー関数内から呼び出す必要があります。

一方、仮想関数は、関数ポインタのオーバーヘッドがいくらかあるかもしれませんが、物事を劇的に単純化します。

編集:eddietreeに似ている別の方法があります: C + +仮想関数対メンバー関数ポインタ(パフォーマンス比較)

0
fishermanhat

"Run time Polymorphism"をサポートするための仮想メソッドが必要です。ポインタまたは基本クラスへの参照を使用して派生クラスオブジェクトを参照するときは、そのオブジェクトの仮想関数を呼び出して、派生クラスのバージョンの関数を実行できます。

0
rashedcs