1

次のサンプル コードを検討してください。

class Base {
public:
    void f();
    virtual void vf();
};

class Derived : public Base {
public:
    void f();
    void vf();
};

#include <iostream>
using namespace std;

void Base::f() {
    cout << "Base f()" << endl;
}

void Base::vf() {
    cout << "Base vf()" << endl;
}

void Derived::f() {
    cout << "Derived f()" << endl;
}

void Derived::vf() {
    cout << "Derived vf()" << endl;
}

int main()
{
    Base b1;
    Derived d1;
    b1.f();
    b1.vf();
    d1.f();
    d1.vf();

    Derived d2;     // Derived object
    Base* bp = &d2; // Base pointer to Derived object
    bp->f();    // Base f()
    bp->vf();   // which vf()?

    return 0;
}

実行の出力は次のとおりです。

Base f()

Base vf()

Derived f()

Derived vf()

Base f()

Derived vf()

質問:

  1. Base* bp = &d2では、オブジェクトの型はコンパイル時に認識されます。では、どの関数を使用するかの決定はbp->vf();、コンパイル時に行うこともできますよね?

  2. オブジェクトの型はコンパイル時にわかっているので、仮想関数の力はこのサンプル プログラムで使用されているのでしょうか。

4

4 に答える 4

4

Base* bp = &d2では、オブジェクトの型はコンパイル時に認識されます。では、どの関数を使用するかの決定はbp->vf();、コンパイル時に行うこともできますよね?

はい。
これは、最新のスマート コンパイラのほとんどが実行できるコンパイラ最適化の一部です。

コンパイラがコンパイル時に呼び出す関数を決定できる場合は、そのようにします。これは、コンパイラがコンパイル時または実行時に呼び出す正確な関数を検出できるかどうかに完全に依存しますが、仮想化により、プログラムに必要な動作が保証されます。

オブジェクトの型はコンパイル時にわかっているので、仮想関数の力はこのサンプル プログラムで使用されているのでしょうか。

コンパイラに完全に依存します。最近のほとんどのコンパイラは、コンパイル時にこの関数呼び出しを評価できます。

于 2011-12-14T15:59:16.823 に答える
3

このプログラムは自明であり、仮想関数 (または、より一般的にはポリモーフィズム) の能力を十分に発揮していません。次の変更を検討してください。

// add second derived class
class Derived2 : public Base {
public:
    void vf() { std::cout << "Derived2 vf()" << std::endl; }
};

// in main
int user_choice;
std::cin >> user_choice;
Base * ptr;
if (user_choice == 0)
    ptr = new Derived();
else
    ptr = new Derived2();
ptr->vf();

ここで、クラスの選択はユーザー入力に依存します。コンパイラーには、呼び出しに到達ptrしたときに実際にどのタイプのオブジェクトが指されるかを予測する方法がありません。ptr->vf();

于 2011-12-14T16:06:20.553 に答える
1

In the line Base* bp = &d2, the object type is known at compile time. Then the decision of which function to use in the case of bp->vf(); can also be made at compile time right?

いいえ、使用する関数の決定は、そのオブジェクトへのポインター/参照のタイプではなく、オブジェクトのタイプに基づいて実行時に動的に行われます。これは動的バインディングと呼ばれます。コンパイラは、virtual pointerまたはvptr仮想テーブルと呼ばれるテーブルを指す隠しポインタを保持します。少なくとも 1 つの仮想関数を持つクラスごとに 1 つの仮想テーブルが存在します (クラス用に作成されたオブジェクトの数に関係なく)。仮想テーブルには、クラスの仮想関数のアドレスが含まれています。

Since the object type is known at compile time itself, is the power of virtual functions used in this sample program?

実際、指されているオブジェクトはコンパイル時に認識されない場合があります。以下に示すように、基本クラスのポインターをパラメーターとして受け取るメソッドの例を見てみましょう。

void draw(Shape *bp)
{
    bp->draw();
}

この場合、実際のオブジェクトは、 から派生した任意の形状である可能性がありますShape。ただし、描画される形状は、渡されたオブジェクトの実際のタイプによって異なります。

于 2011-12-14T16:15:37.763 に答える
1

ええと...両方の質問に対してYESとNOです。それは、議論している抽象化レベルによって異なります。

  • 言語の観点からすると、1) は誤解です。の型d2はわかっていますが、 に割り当てられる d2 アドレスになると、 からへbpの変換が行われます。これ以降、bp の静的型は(それがその宣言であるため) であり、動的型はその Derived を指します (それは、オブジェクトに関連付けられたランタイム型情報が参照するものであるため)。それ以降、bp を介したすべての操作は Base* をタイプとして想定し、すべての仮想関数のリダイレクトを必要とします。Derived*Base*Base*

  • コンパイラの観点からすると、特定の最適化を行うことができます。また、すべての関数で pb が常に Derived を指しているため、実際には仮想リダイレクトをスキップできます。しかし、それはあなたの特定の例が構造化されている方法によるものであり、言語機能によるものではありません。

于 2011-12-14T16:12:53.670 に答える