以下に示す構造体定義で...
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
Base::vf
が宣言されているのと同じ名前、parameter-type-list(8.3.5)、cv-qualification、およびref-qualifier(または同じものがない)で、Derived::vf
も仮想(- そのように宣言されているかどうか)そして、Base::vf
をオーバーライドします。
いいえ、派生クラスの仮想関数オーバーライドのvirtual
キーワードは必要ありません。しかし、関連する落とし穴に言及する価値があります。仮想関数をオーバーライドできないことです。
オーバーライドの失敗は、派生クラスの仮想関数をオーバーライドするつもりであるが、署名でエラーを作成して新しいおよび異なる仮想機能。この関数は、基本クラス関数のoverloadであるか、名前が異なる場合があります。派生クラスの関数宣言で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 を参照してください。
"virtual"キーワードを追加すると読みやすくなりますが、必須ではありません。基本クラスで仮想と宣言され、派生クラスで同じシグネチャを持つ関数は、デフォルトで「仮想」と見なされます。
派生クラスにvirtual
を記述する場合、または省略する場合、コンパイラーに違いはありません。
ただし、この情報を取得するには、基本クラスを調べる必要があります。したがって、この関数が仮想であることを人間に見せたい場合は、派生クラスにもvirtual
キーワードを追加することをお勧めします。
テンプレートがあり、テンプレートパラメータとして基本クラスを取得し始めると、かなりの違いがあります。
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);
}
それの楽しい部分は、インターフェイスと非インターフェイス関数を定義できるようになったことです後でクラスを定義します。これは、ライブラリ間のインターワーキングインターフェイスに役立ちます(single libraryの標準設計プロセスとしてこれに依存しないでください)。すべてのクラスでこれを許可しても、費用はかかりません。必要に応じてtypedef
Bに変更することもできます。
これを行う場合、コピー/移動コンストラクターをテンプレートとして宣言することもできます。異なるインターフェイスから構築できるようにすることで、異なるB<>
タイプ間で「キャスト」できます。
t_hello()
にconst A&
のサポートを追加する必要があるかどうかは疑問です。この書き換えの通常の理由は、主にパフォーマンス上の理由から、継承ベースの特殊化からテンプレートベースの特殊化に移行することです。古いインターフェイスを引き続きサポートする場合、古い使用を検出(または抑止)することはできません。
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キーワードを含めます。なぜなら、