安全なダウンキャスト、シンプルさ、簡潔さのための仮想メソッドが必要です。
これが仮想メソッドの機能です。明らかにシンプルで簡潔なコードを使用して安全にダウンキャストし、他の方法では複雑で冗長なコードで安全でない手動キャストを回避します。
非仮想メソッド⇒静的バインディング
次のコードは意図的に「誤った」ものです。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
として宣言virtual
静的に既知の型にExpression
各呼び出しはオブジェクトの実際の型は、これが何であるかをチェックし、関連の実装を呼び出すことを保証する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;
}
ここでは6.86
、仮想メソッドがvirtualと呼ばれているため、出力は本来の状態です。これは、呼び出しの動的バインディングとも呼ばれます。小さなチェックが実行され、オブジェクトの実際の動的な型を見つけ、その動的な型に関連するメソッドの実装が呼び出されます。
関連する実装は、最も具体的な(最も派生した)クラスの実装です。
ここで派生クラスのメソッド実装はマークされていないがvirtual
、代わりにマークされていることに注意してくださいoverride
。マークすることもできますvirtual
が、自動的に仮想になります。のoverride
キーワードにより、一部の基本クラスにそのような仮想メソッドがない場合、エラーが発生します(これは望ましいことです)。
仮想メソッドなしでこれを行うことの醜さ
なし 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つは、上記のように安全でないダウンキャスト、複雑さ、および冗長性に遭遇した場合、仮想メソッドが実際に役立つことがあります。