安全なダウンキャスト、単純さ、および簡潔さのための仮想メソッドが必要です。
それが仮想メソッドの役割です。明らかにシンプルで簡潔なコードを使用して安全にダウンキャストし、より複雑で冗長なコードでの危険な手動キャストを回避します。
非仮想メソッド ⇒ 静的バインド =======================================
次のコードは、意図的に「正しくない」ものです。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;
}
「bad」とコメントされた行では、Expression::value
メソッドが呼び出されます。これは、静的に既知の型 (コンパイル時に既知の型) がExpression
であり、value
メソッドが仮想ではないためです。
仮想メソッド ⇒ 動的バインディング。======================================
静的に既知の型でvalue
asを宣言すると、各呼び出しでオブジェクトの実際の型が確認され、その動的型に関連する実装が呼び出されることが保証されます。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;
}
ここでは、仮想メソッドが virtual と呼ば6.86
れるため、出力は本来あるべきものになります。これは、呼び出しの動的バインディングとも呼ばれます。オブジェクトの実際の動的タイプを見つけて、少しチェックが実行され、その動的タイプに関連するメソッド実装が呼び出されます。
関連する実装は、最も具体的な (最も派生した) クラスの実装です。
ここでの派生クラスのメソッド実装はマークされていませんがvirtual
、代わりにマークされていることに注意してくださいoverride
。それらはマークできますvirtual
が、自動的に仮想になります。このoverride
キーワードにより、一部の基本クラスにそのような仮想メソッドがない場合にエラーが発生することが保証されます (これは望ましいことです)。
仮想メソッドなしでこれを行うことの醜さ ========================================= ========
それがなければ、動的バインディングのDo It Yourselfvirtual
バージョンを実装する必要があります。一般に、安全でない手動のダウンキャスト、複雑さ、および冗長性を伴うのはこれです。
ここに示すように、単一の関数の場合、オブジェクトに関数ポインターを格納し、その関数ポインターを介して呼び出すだけで十分ですが、それでも安全でないダウンキャスト、複雑さ、および冗長性が含まれます。
#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 つは、上記のような安全でないダウンキャスト、複雑さ、および冗長性に遭遇した場合、多くの場合、1 つまたは複数の仮想メソッドが実際に役立つということです。