web-dev-qa-db-ja.com

派生クラスから仮想関数を削除する

特定の派生クラスで使用してはならない仮想基本クラス関数があります。それを「削除」する方法はありますか?もちろん、空の定義を与えることもできますが、使用しようとするとコンパイル時エラーがスローされます。 C++ 11 delete指定子は私が望んでいるように見えますが、

class B
{
    virtual void f();
};

class D : public B
{
    virtual void f() = delete; //Error
};

コンパイルされません。 gccは、少なくとも、削除されていない基本バージョンを持つ関数を明示的に削除させません。同じ機能を取得する別の方法はありますか?

22
Matt Phillips

標準では許可されていませんが、次の2つの回避策のいずれかを使用して同様の動作を実現できます。

1つ目は、usingメソッドの可視性をprivateに変更するを使用して、他のユーザーが使用できないようにすることです。そのソリューションの問題は、スーパークラスのポインターでメソッドを呼び出してもコンパイルエラーが発生しないことです。

class B
{
public:
    virtual void f();
};

class D : public B
{
private:
    using B::f;
};

Dsメソッドを呼び出すときにコンパイル時にエラーが発生するという、これまでに見つけた最良の解決策は、static_assertを、false_typeから継承する汎用構造体とともに使用することです。誰もメソッドを呼び出さない限り、構造体は未定義のままであり、static_assertは失敗しません。

メソッドが呼び出された場合ただし、構造体は定義されており、その値はfalseであるため、static_assertは失敗します。

メソッドが呼び出されない場合ですが、スーパークラスのポインターでそれを呼び出そうとすると、Dsメソッドが定義されておらず、undefined referenceコンパイルエラーが発生します。

template <typename T>
struct fail : std::false_type 
{
};

class B
{
public:
    virtual void f() 
    {
    }
};

class D : public B
{
public:
    template<typename T = bool>
    void
    f()
    {
        static_assert (fail<T>::value, "Do not use!");
    }
};

別の回避策はメソッドの使用時に例外をスローするですが、これはrun-timeでのみスローされます。

35
Theolodis

標準では、正当な理由により、派生クラスの基本クラスのメンバーを削除することはできません。
そうすると、継承、特に「is-a」関係が壊れます。

関連する理由により、基本クラスで削除された関数を派生クラスで定義することはできません。
フックは基本クラスコントラクトの一部ではなくなったため、もはや保持されていない以前の保証に依存することがなくなります。

トリッキーにしたい場合は、エラーを強制できますが、コンパイル時ではなくリンク時でなければなりません。
メンバー関数を宣言しますが、定義しないでください(ただし、これは仮想関数での動作が100%保証されているわけではありません)。
以前の警告 __attribute__ ((deprecated)) のGCC非推奨属性も確認してください。
詳細および同様のMSマジックについて: C++は非推奨としてマーク

10
Deduplicator

「私は特定の派生クラスでは決して使用すべきではない仮想基本クラス関数を持っています。」

いくつかの点でそれは矛盾です。仮想関数の要点は、基本クラスによって提供されるcontractのさまざまな実装を提供することです。あなたがしようとしているのは、契約を壊すことです。 C++言語は、そうするのを防ぐように設計されています。これが、オブジェクトをインスタンス化するときに純粋な仮想関数の実装を強制する理由です。そして、それがcontractの一部を削除させない理由です。

起こっていることは、良いことです。それはおそらく、不適切な設計の選択を実装することを妨げています。

ただし:

何もしない空の実装が適切な場合があります。

void MyClass::my_virtual_function()
{
    // nothing here
}

または、「失敗」ステータスを返す空の実装:

bool MyClass::my_virtual_function()
{
    return false;
}

それはあなたが何をしようとしているのかに依存します。おそらく、誰かが達成しようとしていることについてより多くの情報を提供できれば、誰かがあなたを正しい方向に向けることができます。

[〜#〜]編集[〜#〜]

考えてみれば、特定の派生型の関数を呼び出さないようにするために、呼び出し元はどの型を呼び出しているかを知る必要があります。基本クラス参照/ポインターを呼び出す重要な点は、どの派生型が呼び出しを受け取るかわからないということです。

3
Galik

あなたができることは単に派生した実装で例外を投げることです。たとえば、Javaコレクションフレームワークはこれを非常に過剰に実行します:不変のコレクションに対して更新操作が実行されると、対応するメソッドは単にUnsupportedOperationExceptionをスローします。C++でも同じことができます。

もちろん、これは実行時にのみ悪意のある関数の使用を示します。コンパイル時ではありません。ただし、仮想メソッドでは、多態性のため、コンパイル時にこのようなエラーをキャッチすることはできません。例えば。:

B* b = new D();
b.f();

ここでは、DB*変数に格納します。したがって、fDを呼び出すことが許可されていないことをコンパイラに伝える方法があったとしても、コンパイラはBしか認識しないため、ここでこのエラーを報告できません。

0
gexicide

特定の派生クラスで使用してはならない仮想基本クラス関数があります。

C++ 11には、仮想関数がオーバーライドされないようにするキーワードfinalが用意されています。

見て: http://en.cppreference.com/w/cpp/language/final

class B
{
  virtual void f() final;
};

class D : public B
{
  // virtual void f();  // a compile-time error
  // void f() override; // a compile-time error
  void f(); // non-virtual function, it's ok
};
0
sergs