5

virtualC++のキーワードの目的がわかりません。C と Java はよく知っていますが、C++ は初めてです

ウィキペディアより

オブジェクト指向プログラミングでは、仮想関数または仮想メソッドは、同じシグネチャを持つ関数によって継承クラス内で動作をオーバーライドできる関数またはメソッドです。

virtualただし、キーワードを使用せずに、以下に示すようにメソッドをオーバーライドできます

#include <iostream>

using namespace std;

class A {
    public:
        int a();
};

int A::a() {
    return 1;   
}

class B : A { 
    public:
        int a();
};

int B::a() {
    return 2;
}

int main() {
    B b;
    cout << b.a() << endl;
    return 0;
}

//output: 2

以下に示すように、関数 A::a は B::a で正常にオーバーライドされます。virtual

私の混乱を悪化させているのは、これもウィキペディアからの仮想デストラクタに関するこの声明です。

次の例に示すように、最も派生したクラスのデストラクタが常に呼び出されるようにするには、C++ 基本クラスに仮想デストラクタを設定することが重要です。

virtualまた、親のデストラクタを呼び出すようにコンパイラに指示しますか? virtualこれは、「関数をオーバーライド可能にする」という私の当初の理解とは大きく異なるようです

4

9 に答える 9

16

次の変更を行うと、その理由がわかります。

#include <iostream>

using namespace std;

class A {
    public:
        int a();
};

int A::a() {
    return 1;   
}

class B : public A { // Notice public added here
    public:
        int a();
};

int B::a() {
    return 2;
}

int main() {
    A* b = new B(); // Notice we are using a base class pointer here
    cout << b->a() << endl; // This will print 1 instead of 2
    delete b; // Added delete to free b
    return 0;
}

さて、意図したとおりに機能させるには:

#include <iostream>

using namespace std;

class A {
    public:
        virtual int a(); // Notice virtual added here
};

int A::a() {
    return 1;   
}

class B : public A { // Notice public added here
    public:
        virtual int a(); // Notice virtual added here, but not necessary in C++
};

int B::a() {
    return 2;
}

int main() {
    A* b = new B(); // Notice we are using a base class pointer here
    cout << b->a() << endl; // This will print 2 as intended
    delete b; // Added delete to free b
    return 0;
}

仮想デストラクタについて含めたメモはまさに正しいです。あなたのサンプルには、クリーンアップする必要があるものは何もありませんが、A と B の両方にデストラクタがあるとします。それらが仮想としてマークされていない場合、どれが基底クラス ポインターで呼び出されるのでしょうか? ヒント: これは、仮想とマークされていないときに a() メソッドが行ったのとまったく同じように機能します。

于 2009-12-01T21:26:17.190 に答える
9

以下のように考えることができます。

Java のすべての関数は仮想です。関数を持つクラスがあり、その関数を派生クラスでオーバーライドすると、呼び出しに使用する変数の宣言された型に関係なく、関数が呼び出されます。

一方、C++ では、必ずしも呼び出されるとは限りません。

基本クラス Base と派生クラス Derived があり、それらの両方に「foo」という名前の非仮想関数がある場合、

Base * base;
Derived *derived;

base->foo(); // calls Base::foo
derived->foo(); // calls Derived::foo

foo が仮想の場合、両方とも Derived::foo を呼び出します。

于 2009-12-01T21:27:12.567 に答える
2

仮想もコンパイラに親のデストラクタを呼び出すように指示しますか? これは、「関数をオーバーライド可能にする」という仮想の最初の理解とは大きく異なるようです。

あなたの元の理解とあなたの新しい理解はどちらも間違っています。

  • メソッド (関数と呼びます) は常にオーバーライド可能です。仮想、純粋、非仮想、または何かに関係なく。
  • 親デストラクタは常に呼び出されます。コンストラクターも同様です。

「仮想」は、基本クラスへのポインター型のポインターを介してメソッドを呼び出す場合にのみ違いを生みます。あなたの例ではポインターをまったく使用していないため、仮想はまったく違いがありません。

aA へのポインター型の変数、つまり を使用する場合、A* a;A へのポインター型の他の変数だけでなく、B へのポインター型の変数も割り当てることができます。これは、B が A から派生しているためです。 .

A* a; 
B* b;

b = new B(); // create a object of type B. 
a = b;       // this is valid code. a has still the type pointer-to-A, 
             // but the value it holds is b, a pointer to a B object.

a.a();       // now here is the difference. If a() is non-virtual, A::a()
             // will be called, because a is of type pointer-to-A. 
             // Whether the object it points to is of type A, B or
             // something entirely different doesn't matter, what gets called
             // is determined during compile time from the type of a.

a.a();       // now if a() is virtual, B::a() will be called, the compiler
             // looks during runtime at the value of a, sees that it points
             // to a B object and uses B::a(). What gets called is determined
             // from the type of the __value__ of a.
于 2009-12-01T23:42:34.393 に答える
2

仮想とは、実際のメソッドが、変数の宣言に使用した型ではなく、インスタンス化されたクラスに基づいて実行時に決定されることを意味します。あなたの場合、これは静的オーバーライドであり、作成されたオブジェクトの実際のタイプに関係なく、クラス B に対して定義されたメソッドに適用されます

于 2009-12-01T21:28:40.193 に答える
1

Consultutah(および私が入力している間は他のポインター;))が言うように、基本クラスのポインターを使用する場合は、仮想が必要です。

仮想がないため、呼び出す必要のあるメソッド(基本クラスのメソッドまたは派生クラスのメソッド)を知るためのチェックを保存できます。ただし、この時点では、正しい動作だけで、パフォーマンスについて心配する必要はありません。

派生クラスはヒープ上で他の変数を宣言する可能性があり(つまり、キーワード'new'を使用)、それを削除できる必要があるため、仮想デストラクタは特に重要です。

ただし、C ++では、たとえばJavaよりも派生が少なくなる傾向があり(同様の用途でテンプレートを使用することがよくあります)、おそらくそれについて気にする必要はありません。また、オブジェクトをヒープ上で宣言しない場合( "A * a = new A();"の代わりに "A a;")、それについても心配する必要はありません。もちろん、これはあなたが何を/どのように開発するか、そして他の誰かがあなたのクラスを派生させることを計画しているかどうかに大きく依存します。

于 2009-12-01T21:34:34.700 に答える
1

以下に示すように、関数 A::a は仮想を必要とせずに B::a で正常にオーバーライドされます

うまくいくかもしれませんし、うまくいかないかもしれません。あなたの例では機能しますが、それは、へのBポインターではなく、オブジェクトを直接作成して使用するためですAC++ FAQ Lite、20.3を参照してください。

仮想もコンパイラに親のデストラクタを呼び出すように指示しますか?

派生クラスのオブジェクトを指す基本クラスのポインターを削除し、基本デストラクタと派生デストラクタの両方が実行されると予想される場合は、仮想デストラクタが必要です。C++ FAQ Lite、20.7を参照してください。

于 2009-12-01T21:28:33.797 に答える
0

((A*)&b).a() を試して、何が呼び出されるかを確認してください。

virtual キーワードを使用すると、オブジェクトを抽象的な方法 (基本クラス ポインターを介した IE) で処理しながら、子孫コードを呼び出すことができます...

別の言い方をすれば、仮想キーワードは「古いコードが新しいコードを呼び出せるようにする」ということです。A を操作するコードを作成したことがあるかもしれませんが、仮想関数を介して、そのコードは B の新しい a() を呼び出すことができます。

于 2009-12-01T21:27:07.743 に答える
0

B をインスタンス化したが、それを A のインスタンスとして保持したとします。

A *a = new B();

a() の実装が呼び出される関数 a() を呼び出しましたか?

a() が仮想でない場合、A が呼び出されます。a() が virtual の場合、a() のインスタンス化されたサブクラス バージョンは、保持方法に関係なく呼び出されます。

B のコンストラクターが配列または開いているファイルに大量のメモリを割り当てた場合、

delete a;

B のデストラクタがどのように保持されているかに関係なく、それが基本クラスやインターフェイスなどによって呼び出されるようにします。

ところで、良い質問です。

于 2009-12-01T21:29:47.637 に答える
0

私はいつもそれをチェスの駒のように考えています (オブジェクト指向での私の最初の実験)。

チェス盤には、すべての駒へのポインターが保持されます。空の四角は NULL ポインターです。しかし、それが知っているのは、各ポインターがチェスの駒を指していることだけです。取締役会はこれ以上の情報を知る必要はありません。しかし、ピースが動かされたとき、ボードはそれが有効な動きであることを知りません。それぞれのピースはどのように動くかについて異なる特徴を持っているからです。そのため、ボードは、移動が有効かどうかをピースで確認する必要があります。

Piece*    board[8][8];

CheckMove(Point const& from,Point const& too)
{
    Piece*  piece = board[from.x][from.y];
    if (piece != NULL)
    {
        if (!piece->checkValidMove(from,too))
        {    throw std::exception("Bad Move");
        }
        // Other checks.
    }
}

class Piece
{
    virtual bool checkValidMove(Point const& from,Point const& too)  = 0;
};

class Queen: public Piece
{
    virtual bool checkValidMove(Point const& from,Point const& too) 
    {
         if (CheckHorizontalMove(from,too) || CheckVerticalMoce(from,too) || CheckDiagonalMove(from,too))
         {
             .....
         }
    }
}
于 2009-12-02T00:31:16.243 に答える