以下に示す構造体の定義を使用して...
struct A {
virtual void hello() = 0;
};
アプローチ#1:
struct B : public A {
virtual void hello() { ... }
};
アプローチ#2:
struct B : public A {
void hello() { ... }
};
hello関数をオーバーライドするこれらの2つの方法の間に違いはありますか?
以下に示す構造体の定義を使用して...
struct A {
virtual void hello() = 0;
};
アプローチ#1:
struct B : public A {
virtual void hello() { ... }
};
アプローチ#2:
struct B : public A {
void hello() { ... }
};
hello関数をオーバーライドするこれらの2つの方法の間に違いはありますか?
回答:
それらはまったく同じです。最初のアプローチではより多くのタイピングが必要であり、より明確になる可能性があること以外は、それらの間に違いはありません。
関数の「仮想性」は暗黙的に伝達されますが、virtual
キーワードを明示的に使用しない場合、少なくとも1つのコンパイラを使用すると警告が生成されるため、コンパイラを静かに保つためだけに使用したい場合があります。
virtual
キーワードを含む純粋なスタイルの観点から、機能が仮想であることをユーザーに明らかに「宣伝」します。これは、Aの定義をチェックせずにBをさらにサブクラス化するすべての人にとって重要です。深いクラス階層の場合、これは特に重要になります。
virtual
キーワードは、派生クラスでは必要ありません。これは、C ++ Draft Standard(N3337)からのサポートドキュメントです(強調は私のものです):
10.3仮想関数
2仮想メンバ関数は場合
vf
クラス内で宣言されたBase
クラスにDerived
直接的または間接的に誘導される、からBase
メンバ関数、vf
同じ名前、パラメータ型リスト(8.3.5)、CV-資格、およびREF-修飾子(Base::vf
宣言されているのと同じか、または存在しない)場合Derived::vf
も、仮想であり(宣言されているかどうかにかかわらず)、オーバーライドされますBase::vf
。
いいえ、virtual
派生クラスの仮想関数オーバーライドのキーワードは必要ありません。ただし、関連する落とし穴、つまり仮想関数のオーバーライドの失敗について言及する価値があります。
派生クラスの仮想関数をオーバーライドする予定であるが、シグネチャにエラーを作成して、新しい別の仮想関数を宣言する場合、オーバーライドの失敗が発生します。この関数は、基本クラス関数のオーバーロードであるか、名前が異なる場合があります。virtual
派生クラスの関数宣言でキーワードを使用するかどうかに関係なく、コンパイラーは、基本クラスの関数をオーバーライドするつもりであると判断できません。
ただし、この落とし穴はC ++ 11の明示的なオーバーライド言語機能によってありがたいことに対処されています。これにより、ソースコードはメンバー関数が基本クラス関数をオーバーライドすることを意図していることを明確に指定できます。
struct Base {
virtual void some_func(float);
};
struct Derived : Base {
virtual void some_func(int) override; // ill-formed - doesn't override a base class method
};
コンパイラーはコンパイル時エラーを発行し、プログラミングエラーはすぐに明らかになります(おそらく、Derivedの関数は、 float
は引数としてをでした)。
WP:C ++ 11を参照してください。
テンプレートがあり、基本クラスをテンプレートパラメータとして取得する場合は、かなりの違いがあります。
struct None {};
template<typename... Interfaces>
struct B : public Interfaces
{
void hello() { ... }
};
struct A {
virtual void hello() = 0;
};
template<typename... Interfaces>
void t_hello(const B<Interfaces...>& b) // different code generated for each set of interfaces (a vtable-based clever compiler might reduce this to 2); both t_hello and b.hello() might be inlined properly
{
b.hello(); // indirect, non-virtual call
}
void hello(const A& a)
{
a.hello(); // Indirect virtual call, inlining is impossible in general
}
int main()
{
B<None> b; // Ok, no vtable generated, empty base class optimization works, sizeof(b) == 1 usually
B<None>* pb = &b;
B<None>& rb = b;
b.hello(); // direct call
pb->hello(); // pb-relative non-virtual call (1 redirection)
rb->hello(); // non-virtual call (1 redirection unless optimized out)
t_hello(b); // works as expected, one redirection
// hello(b); // compile-time error
B<A> ba; // Ok, vtable generated, sizeof(b) >= sizeof(void*)
B<None>* pba = &ba;
B<None>& rba = ba;
ba.hello(); // still can be a direct call, exact type of ba is deducible
pba->hello(); // pba-relative virtual call (usually 3 redirections)
rba->hello(); // rba-relative virtual call (usually 3 redirections unless optimized out to 2)
//t_hello(b); // compile-time error (unless you add support for const A& in t_hello as well)
hello(ba);
}
それの楽しい部分は、後でクラスを定義するためにインターフェースおよび非インターフェース関数を定義できるようになったことです。これは、ライブラリ間のインターフェイスの相互作用に役立ちます(単一のライブラリの標準設計プロセスとしてこれに依存しないでください)。すべてのクラスでこれを許可しても、費用はかかりません。typedef
。必要に応じ Bに変更することもできます。
これを行う場合は、コピー/移動コンストラクターをテンプレートとして宣言することもできます。異なるインターフェースから構築できるようにすると、異なるB<>
タイプ間で「キャスト」できます。
それはあなたがのためのサポートを追加する必要があるかどうかは疑問だconst A&
中をt_hello()
。この書き直しの通常の理由は、主にパフォーマンス上の理由から、継承ベースの特殊化からテンプレートベースの特殊化に移行することです。古いインターフェースを引き続きサポートしている場合、古い使用法をほとんど検出(または抑止)できません。
virtual
キーワードはそれらをオーバーライドするために、基本クラスの機能を追加する必要があります。あなたの例でstruct A
は、は基本クラスです。virtual
派生クラスでこれらの関数を使用する意味はありません。ただし、派生クラス自体も基本クラスにしたい場合、およびその関数をオーバーライド可能にしたい場合は、virtual
そこに配置する必要があります。
struct B : public A {
virtual void hello() { ... }
};
struct C : public B {
void hello() { ... }
};
ここではC
から継承しB
てB
いるため、基本クラスではなく(派生クラスでもあり)、C
派生クラスです。継承図は次のようになります。
A
^
|
B
^
|
C
したがって、virtual
子を持つ可能性のある基本クラス内の関数の前に配置する必要があります。virtual
あなたの子供があなたの機能をオーバーライドできるようにします。virtual
派生クラス内の関数の前に配置しても問題はありませんが、必須ではありません。ただし、派生クラスから継承したい場合は、オーバーライドするメソッドが期待どおりに機能しないことに不満を感じるため、この方法をお勧めします。
したがってvirtual
、基本クラスの関数をオーバーライドする必要がある子がクラスにないことが確実でない限り、継承に関係するすべてのクラスの関数の前に置きます。それは良い習慣です。
私は確かに子クラスのVirtualキーワードを含めます。