以下の例を検討してください。
#include <iostream>
using namespace std;
class base
{
public:
virtual int func()
{
cout << "vfunc in base class\n";
return 0;
}
};
class derived: public base
{
public:
double func()
{
cout << "vfunc in derived class\n";
return 0;
}
};
int main()
{
base *bptr = new derived;
bptr->func();
return 0;
}
コンパイラーは、オーバーライドされた関数のタイプが競合しているという上記のコードに対してエラーを出します。派生クラスの関数を異なる戻り値の型でオーバーライドできないのはなぜですか?
関数をオーバーライドするには、基本クラスの仮想メソッドを派生クラスで再定義する必要があると思います。メソッドを再定義するには、メソッドのシグネチャが同じである必要があります。戻り値の型はシグネチャの一部ではないので、戻り値の型に違いがあっても、メソッドは再定義されますか?上記のコードの場合、仮想関数func
は、戻り値の型が異なる派生クラスで再定義されます。しかし、コンパイラはエラーをスローします。私の理解は正しいですか?
オーバーライドとは、基本的に、ポインタが指す実際のオブジェクトに応じて、BaseクラスメソッドまたはDerivedクラスメソッドが実行時に呼び出されることを意味します。
それは以下を意味します:
i.e:Baseクラスメソッドを呼び出すことができるすべての場所は、呼び出しコードを変更せずにDerivedクラスメソッドを呼び出すことで置き換えることができます。
これを実現する唯一の方法は、オーバーライドする仮想メソッドの戻り値の型を制限して、Baseクラスと同じ型またはそれから派生した型(共変戻り値型)を返すようにすることであり、規格はこの条件を適用します。
上記の条件が満たされていない場合は、新しい機能を追加して既存のコードを壊すウィンドウが残ります。
仮想関数をオーバーライドするには、戻り値が完全に同じである必要があります*。 C++はnotをここで自動的にdouble
とint
の間で変換します-結局のところ、派生クラスポインターから呼び出すときに必要な戻り型をどのようにして知るのでしょうか。シグネチャの一部(パラメータ、定数など)を変更すると、戻り値も変更できることに注意してください。
*-厳密に言えば、それは「共変」でなければなりません。つまり、返す型は、親関数の戻り型のサブセットでなければなりません。たとえば、親クラスがbase *
を返す場合、derived *
を返すことができます。 derived
sは同時にbase
sでもあるため、コンパイラーはこの方法でオーバーライドできます。ただし、int
やdouble
などのまったく関係のない型を返すことはできません。暗黙的な変換があるからといって、コンパイラーがこの種のオーバーライドを行えるようになるとは限りません。
オーバーライドしたい場合は、テンプレートを使用してみてください。
以下を参照してください。
#include <iostream>
using namespace std;
class base
{
public:
template<typename X> X func()
{
cout << "vfunc in base class\n";
return static_cast<X>(0);
}
};
class derived: public base
{
public:
template<typename X> X func()
{
cout << "vfunc in derived class\n";
return static_cast<X>(2);
}
};
int main()
{
derived *bptr = new derived;
cout << bptr->func<int>() << endl;
cout << dynamic_cast<base*>(bptr)->func<int>() << endl;
derived *bptr2 = new derived;
cout << bptr->func<double>() << endl;
cout << dynamic_cast<base*>(bptr)->func<int>() << endl;
return 0;
}
もちろん、このように2つの異なるクラスで宣言する必要はありません。
class base
{
public:
int func()
{
cout << "vfunc in base class\n";
return 0;
}
double func(){
cout << "vfunc for double class\n";
return 2.;
}
};
署名が異なるため、上書きすることはできません。オーバーライドの基本的な目的は多態性ですが、上記の例では不可能です