実装された純粋な仮想関数


176

私の基本的な理解は、純粋仮想関数の実装がないということですが、純粋仮想関数の実装があるかもしれないと言われました。

class A {
public:
    virtual void f() = 0;
};

void A::f() {
    cout<<"Test"<<endl;
}

上記のコードは大丈夫ですか?

それを実装を備えた純粋な仮想関数にする目的は何ですか?

回答:


215

純粋なvirtual関数は、直接インスタンス化される派生型で実装する必要がありますが、基本型でも実装を定義できます。(呼び出すことによって、(アクセス権限で許可されている場合)派生クラスが明示的に完全スコープ名を使用して、基本クラスの実装を呼び出すことができますA::f()-場合はあなたの例ではA::f()なかったですpublicprotected)。何かのようなもの:

class B : public A {

    virtual void f() {
        // class B doesn't have anything special to do for f()
        //  so we'll call A's

        // note that A's declaration of f() would have to be public 
        //  or protected to avoid a compile time problem

        A::f();
    }

};

私が頭の中で考えることができるユースケースは、多かれ少なかれ妥当なデフォルトの動作がある場合ですが、クラスの設計者はデフォルトのような動作が明示的にのみ呼び出されることを望んでいます。また、派生クラスが常に独自の作業を実行するだけでなく、共通の機能セットを呼び出すこともできます。

言語で許可されていても、私が一般的に使用しているものではないことに注意してください(それが可能であるという事実は、ほとんどのC ++プログラマー、経験豊富なプログラマーでも驚きます)。


1
プログラマーを驚かせる理由を追加するのを忘れていました。これは、インライン定義が標準で禁止されているためです。純粋な仮想メソッドの定義はでなければなりませんdeported。(.inlまたは.cppのいずれかで、一般的なファイル命名規則を参照します)。
v.oddou

したがって、この呼び出しメソッドは、静的メソッドメンバーの呼び出しと同じです。Javaのある種のクラスメソッド。
Sany Liew

2
「あまり使われていない」==悪い習慣?私はNVIを実装しようとして、まったく同じ動作を探していました。そして、NVIは私にとって良い習慣のようです。
Saskia 2014

5
A :: f()を純粋にすることは、B f()を実装する必要があることを意味することを指摘する価値があります(そうでない場合、Bは抽象的でインスタンス化できません)。そして、@ MichaelBurrが指摘するように、A :: f()の実装を提供することは、B それを使用してf()を定義することを意味します。
fearless_fool

2
IIRC、Scot Meyerは、彼の古典的な著書「より効果的なC ++」の1つに、この質問のユースケースに関する優れた記事を掲載しています
irsis

75

明確にするために、何が0であるかを誤解しています。仮想関数の後を意味します。

= 0は、派生クラスが実装を提供する必要があることを意味します。基本クラスが実装を提供することはできません。

実際には、仮想関数を純粋(= 0)としてマークすると、誰かがBase :: Function(...)を介して明示的に呼び出さない限り、または基本クラスのコンストラクターが問題の仮想関数を呼び出します。


9
これは誤りです。純粋仮想クラスのコンストラクターでその純粋仮想関数を呼び出すと、純粋仮想呼び出しが行われます。その場合は、実装をお勧めします。
rmn 2010年

@rmn、はい、あなたはコンストラクタでの仮想呼び出しについて正しいです。答えを更新しました。うまくいけば、誰もがそうしないことを知っています。:)
Terry Mahaffey、2010年

3
実際、コンストラクターから基本的な純粋な呼び出しを行うと、実装定義の動作になります。VC ++では、これは_purecallのクラッシュを意味します。
Ofek Shilon

@OfekShilonは正解です-未定義の動作と悪い習慣/コードのリファクタリング(つまり、コンストラクター内の仮想メソッドの呼び出し)の候補も呼び出したくなるでしょう。正しい実装の本体にルーティングする準備ができていない可能性がある仮想テーブルの一貫性に関係していると思います。
teodron

1
コンストラクタとデストラクタでは、仮想関数は仮想ではありません
Jesper Juhl 2017

20

この方法の利点は、派生型がメソッドをオーバーライドすることを強制するだけでなく、デフォルトまたは追加の実装も提供することです。


1
デフォルトの実装がある場合、なぜ強制したいのですか?それは通常の仮想関数のように聞こえます。それが通常の仮想関数である場合は、オーバーライドすることができます。そうでない場合は、デフォルトの実装が提供されます(ベースの実装)。
StackExchange123

19

派生クラスで実行する必要があるコードがあり、それを直接実行したくない-強制的にオーバーライドしたい場合。

あなたのコードは正しいですが、これらはすべて頻繁に使用される機能ではありませんが、通常、純粋な仮想デストラクタを定義しようとしたときにのみ見られます-その場合、実装を提供する必要があります。おもしろいのは、いったんそのクラスから派生すると、デストラクタをオーバーライドする必要がないということです。

したがって、純粋な仮想関数の1つの賢明な使用法は、純粋な仮想デストラクタを「非最終」キーワードとして指定することです。

次のコードは驚くほど正しいです:

class Base {
public:
  virtual ~Base() = 0;
};

Base::~Base() {}

class Derived : public Base {};

int main() { 
  // Base b; -- compile error
  Derived d; 
}

1
いずれにしても、基本クラスのデストラクタは常に仮想と呼ばれ、仮想かどうかと呼ばれます。他の関数では、基本クラスのバージョンが純粋であるかどうかにかかわらず、オーバーライドする仮想関数が基本クラスの実装を呼び出すことを保証できません。
CBベイリー

1
そのコードは間違っています。言語の構文の癖のため、クラス定義の外でdtorを定義する必要があります。

@ロジャー:ありがとう、それは実際に私を助けました-これは私が使用してきたコードであり、MSVCの下で正常にコンパイルされますが、移植性がないと思います。
Kornel Kisielewicz、2010年


4

はい、これは正しいです。あなたの例では、Aから派生するクラスは、インターフェースf()とデフォルト実装の両方を継承します。ただし、派生クラスにメソッドf()を実装するように強制します(Aによって提供されるデフォルト実装を呼び出すだけの場合でも)。

Scott Meyersは、これについて、Effective C ++(2nd Edition)の項目36で説明しています。インターフェイスの継承と実装の継承を区別します。最新版では品番が変更されている場合がございます。


4

本体の有無にかかわらず純粋な仮想関数は、派生型が独自の実装を提供する必要があることを単に意味します。

基本クラスの純粋な仮想関数本体は、派生クラスが基本クラスの実装を呼び出したい場合に役立ちます。


2

'virtual void foo()= 0;' 構文は、現在のクラスにfoo()を実装できないという意味ではありません。また、派生クラスで実装する必要があるという意味でもありません。私を平手打ちする前に、ダイヤモンドの問題を見てみましょう:(暗黙のコード、気にしてください)。

class A
{
public: 
    virtual void foo()=0;
    virtual void bar();
}

class B : public virtual A
{
public:
    void foo() { bar(); }
}

class C : public virtual A
{
public:
    void bar();
}

class D : public B, public C
{}

int main(int argc, const char* argv[])
{
    A* obj = new D();
    **obj->foo();**
    return 0;
}

これで、obj-> foo()を呼び出すとB :: foo()になり、次にC :: bar()になります。

わかりました...純粋な仮想メソッドは派生クラスで実装する必要はありません(foo()はクラスCで実装されていません-コンパイラーはコンパイルします)C ++には多くの抜け穴があります。

私が助けてくれることを願っています:-)


5
すべての派生クラスで実装する必要はありませんが、インスタンス化する予定のすべての派生クラスで実装する必要があります。あなたのC例ではタイプのオブジェクトをインスタンス化することはできません。タイプのオブジェクトDfoofromの実装を取得するため、インスタンス化できますB
YoungJohn

0

実装本体持つ純粋な仮想メソッドを使用する重要なユースケースの1つは、抽象クラスを作成したいが、クラスに適切なメソッドがなく、純粋な仮想にする場合です。この場合、クラスのデストラクタを純粋仮想にして、そのために必要な実装(空の本体も含む)を置くことができます。例として:

class Foo
{
   virtual ~Foo() = 0;
   void bar1() {}
   void bar2(int x) {}
   // other methods
};

Foo::~Foo()
{
}

この手法は、Fooクラスを抽象化し、その結果、クラスを直接インスタンス化することを不可能にします。同時に、Fooクラスを抽象化するための純粋な仮想メソッドを追加していません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.