4

次のコードを理解しようとしています。

#include<iostream>
using namespace std;
class Base {
    public:
        virtual void f(float) { cout << "Base::f(float)\n"; }
};
class Derived : public Base {
    public:
        virtual void f(int) { cout << "Derived::f(int)\n"; }
};
int main() {
    Derived *d = new Derived();
    Base *b = d;
    d->f(3.14F);
    b->f(3.14F);
}

これは印刷します

Derived::f(int)
Base::f(float)

正確な理由はわかりません。

最初の呼び出し d->f(3.14F) は、Derived で関数 f を呼び出します。理由は 100% わかりません。私はこれ(http://en.cppreference.com/w/cpp/language/implicit_cast)を見ました:

浮動小数点型の prvalue は、任意の整数型の prvalue に変換できます。小数部分は切り捨てられます。つまり、小数部分は破棄されます。値が宛先の型に収まらない場合、動作は未定義です

float は int に収まらないため、これを行うことはできません。この暗黙的な変換が許可されるのはなぜですか?

第二に、上記を問題ないと受け入れたとしても、b->f(3.14F) への 2 回目の呼び出しは意味がありません。b->f(3.14F) は仮想関数 f を呼び出しているため、これは動的に解決され、派生オブジェクトである b が指すオブジェクトの動的型に関連付けられた f() を呼び出します。3.14F を int に変換することが許可されているため、最初の関数呼び出しはこれが有効であることを示しているため、(私の理解では) Derived::f(int) 関数を再度呼び出す必要があります。それでも、Base クラスの関数を呼び出します。では、これはなぜですか?

編集:これが私がそれを理解し、自分自身に説明した方法です。

b は Base オブジェクトへのポインターであるため、b を使用して Base オブジェクトのメンバーにアクセスすることしかできません。たとえ b が実際に Derived オブジェクトを指していても (これは標準の OO/継承のものです)。

この規則の唯一の例外は、Base のメンバー関数が virtual として宣言されている場合です。このような場合、Derived オブジェクトはこの関数をオーバーライドし、まったく同じ署名を使用して別の実装を提供できます。これが発生すると、たまたま Base オブジェクトへのポインターを介してメンバー関数にアクセスしている場合でも、この Derived 実装が実行時に呼び出されます。

上記のコード スニペットでは、B::f と D::f のシグネチャが異なるため (一方が float、もう一方が int)、オーバーライドは行われません。したがって、b->f(3.14F) を呼び出すとき、考慮される唯一の関数は、呼び出された元の B::f です。

4

3 に答える 3

11

2 つの関数は異なるシグネチャを持っているためf、 inderivedは仮想関数 in をオーバーライドしませんbaseintとが暗黙的にキャストできるという理由だけで、floatここでは効果がありません。

virtual void f(float) { cout << "Base::f(float)\n"; }
virtual void f(int) { cout << "Derived::f(int)\n"; }

何が起こっているかの手がかりはoverride、C++11 の new キーワードで見ることができます。これは、この種のバグを減らすのに非常に効果的です。

virtual void f(int) override { cout << "Derived::f(int)\n"; }

gccがエラーを生成する元:

virtual void Derived::f(int)' はオーバーライドとマークされていますが、オーバーライドしません

ガチャガチャ

エラー: 'f' は 'override' とマークされていますが、どのメンバー関数もオーバーライドしません

http://en.cppreference.com/w/cpp/language/override

編集:

2 番目のポイントとして、暗黙的に互換性のあるメンバー関数を公開するオーバーfloatロードを実際に公開できます。そのようです:basederived

class Derived : public Base {
public:
    using Base::f;
    virtual void f(int) { cout << "Derived::f(int)\n"; }
};

float をメンバー関数に渡すfと、ベースで定義された関数の近くにバインドされ、次が生成されます。

Base::f(float)
Base::f(float)
于 2012-10-28T11:40:31.017 に答える
2

非表示について考える簡単な方法は次のとおりです-行d->f(3.14F)を見てください。例から:

  1. コンパイラの最初のステップは、クラス名を選択することです。これを行うには、メンバー関数名fが使用されます。パラメータタイプは使用されません。派生が選択されます。
  2. コンパイラの次のステップは、そのクラスからメンバー関数を選択することです。パラメータタイプが使用されます。void Derived :: f(int); は、Derivedクラスの正しい名前とパラメーターを持つ唯一の一致する関数です。
  3. float型からint型への型変換が行われています。
于 2012-10-28T11:54:42.973 に答える
1

これら 2 つの関数の引数の型が異なるため、Derivedクラス内のものは実際にはBase. 代わりにDerived::f隠しますBase::f(現時点では標準を持っていないため、章を引用できません)。

これは、 を呼び出すときd->f(3.14f)に、コンパイラは を考慮さえしないことを意味しますB::f。への呼び出しを解決しますD::f。ただし、 を呼び出す場合b->f(3.14f)、コンパイラが選択できる唯一のバージョンは、それをオーバーライドしないB::fasです。D::f

の読み方If the value can not fit into the destination type, the behavior is undefinedが間違っています。タイプではなくを言います。したがって、値 3.0f は int に適合しますが、3e11 は適合しません。後者の場合、動作は未定義です。あなたの引用の最初の部分は、A prvalue of floating-point type can be converted to prvalue of any integer type.なぜd->f(3.14f)解決されるのかを説明していますD::f(int)- float は実際に整数型に変換できます。

于 2012-10-28T11:47:34.947 に答える