派生クラスの関数のC ++「仮想」キーワード。必要ですか?


221

以下に示す構造体の定義を使用して...

struct A {
    virtual void hello() = 0;
};

アプローチ#1:

struct B : public A {
    virtual void hello() { ... }
};

アプローチ#2:

struct B : public A {
    void hello() { ... }
};

hello関数をオーバーライドするこれらの2つの方法の間に違いはありますか?


65
C ++ 11では、「void hello()override {}」と記述して、仮想メソッドをオーバーライドしていることを明示的に宣言できます。基本仮想メソッドが存在しない場合、コンパイラーは失敗し、子孫クラスに「仮想」を配置するのと同じように読みやすくなります。
ShadowChaser 2013

実際、gccのC ++ 11では、基本クラスがメソッドhello()が仮想であると指定しているため、派生クラスでvoid hello()オーバーライド{}を記述しても問題ありません。言い換えれば、派生クラスでの仮想という言葉の使用は、gcc / g ++の場合はとにかく必要/必須ではありません。(私はRPi 3でgccバージョン4.9.2を使用しています)とにかく、派生クラスのメソッドにキーワードvirtualを含めることをお勧めします。
ウィル

回答:


183

それらはまったく同じです。最初のアプローチではより多くのタイピングが必要であり、より明確になる可能性があること以外は、それらの間に違いはありません。


25
これは本当ですが、Mozilla C ++移植性ガイドでは、「一部のコンパイラ」が警告を発行しないため、仮想を使用することを常に推奨しています。彼らがそのようなコンパイラの例について言及していないのは残念です。
Sergei Tachenov

5
また、明示的に仮想としてマークすることで、デストラクタも仮想にすることを思い出させます。
lfalin 2014

1
言及するだけで、仮想デストラクタにも
Atul

6
@SergeyTachenovによると、クリフォード彼自身の答えに対するコメントによれば、そのようなコンパイラの例はarmccです。
Ruslan

4
@Rasmi、新しい移植性ガイドがここありますが、今はoverrideキーワードを使用することをお勧めします。
Sergei Tachenov 2016

83

関数の「仮想性」は暗黙的に伝達されますが、virtualキーワードを明示的に使用しない場合、少なくとも1つのコンパイラを使用すると警告が生成されるため、コンパイラを静かに保つためだけに使用したい場合があります。

virtualキーワードを含む純粋なスタイルの観点から、機能が仮想であることをユーザーに明らかに「宣伝」します。これは、Aの定義をチェックせずにBをさらにサブクラス化するすべての人にとって重要です。深いクラス階層の場合、これは特に重要になります。


12
これはどのコンパイラですか?
James McNellis、2011

35
@ジェームズ:armcc(ARMデバイス用のARMコンパイラ)
クリフォード

55

virtualキーワードは、派生クラスでは必要ありません。これは、C ++ Draft Standard(N3337)からのサポートドキュメントです(強調は私のものです):

10.3仮想関数

2仮想メンバ関数は場合vfクラス内で宣言されたBaseクラスにDerived直接的または間接的に誘導される、からBaseメンバ関数、vf同じ名前、パラメータ型リスト(8.3.5)、CV-資格、およびREF-修飾子(Base::vf宣言されているのと同じか、または存在しない)場合Derived::vfも、仮想であり(宣言されているかどうかにかかわらず)、オーバーライドされますBase::vf


5
これが断然最良の答えです。
ファンタスティックMr Fox

33

いいえ、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を参照してください。


11

「virtual」キーワードを追加すると読みやすさが向上するため、良い方法ですが、必須ではありません。基本クラスで仮想と宣言され、派生クラスで同じ署名を持つ関数は、デフォルトで「仮想」と見なされます。


7

あなたが書くとき、コンパイラに違いはありません virtual派生クラスでたり、省略したりして。

ただし、この情報を取得するには、基本クラスを調べる必要があります。したがってvirtual、この関数が仮想であることを人間に示したい場合は、派生クラスにもキーワードを追加することをお勧めします。


2

テンプレートがあり、基本クラスをテンプレートパラメータとして取得する場合は、かなりの違いがあります。

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()。この書き直しの通常の理由は、主にパフォーマンス上の理由から、継承ベースの特殊化からテンプレートベースの特殊化に移行することです。古いインターフェースを引き続きサポートしている場合、古い使用法をほとんど検出(または抑止)できません。


1

virtualキーワードはそれらをオーバーライドするために、基本クラスの機能を追加する必要があります。あなたの例でstruct Aは、は基本クラスです。virtual派生クラスでこれらの関数を使用する意味はありません。ただし、派生クラス自体も基本クラスにしたい場合、およびその関数をオーバーライド可能にしたい場合は、virtualそこに配置する必要があります。

struct B : public A {
    virtual void hello() { ... }
};

struct C : public B {
    void hello() { ... }
};

ここではCから継承しBBいるため、基本クラスではなく(派生クラスでもあり)、C派生クラスです。継承図は次のようになります。

A
^
|
B
^
|
C

したがって、virtual子を持つ可能性のある基本クラス内の関数の前に配置する必要があります。virtualあなたの子供があなたの機能をオーバーライドできるようにします。virtual派生クラス内の関数の前に配置しても問題はありませんが、必須ではありません。ただし、派生クラスから継承したい場合は、オーバーライドするメソッドが期待どおりに機能しないことに不満を感じるため、この方法をお勧めします。

したがってvirtual、基本クラスの関数をオーバーライドする必要がある子がクラスにないことが確実でない限り、継承に関係するすべてのクラスの関数の前に置きます。それは良い習慣です。


0

私は確かに子クラスのVirtualキーワードを含めます。

  • 私。読みやすさ。
  • ii。この子クラスはさらに下に派生させることができます。さらに派生したクラスのコンストラクターがこの仮想関数を呼び出さないようにする必要があります。

1
彼は、子関数を仮想としてマークしないと、後で子クラスから派生するプログラマーは、関数が実際には仮想であることに気付かず(基本クラスを見たことがないため)、構築中にそれを呼び出す可能性がある(正しいことを行う場合と行わない場合があります)。
PfhorSlayer 2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.