18

序文:

これは、継承された親の仮想メソッドをオーバーライドする子クラスに適用された場合の C++11 で導入された削除演算子の新しい意味に関するベスト プラクティスに関する質問です。

バックグラウンド:

標準によると、引用されている最初の使用例は、最新のC++11 標準ドラフトの §8.4.3 の例のように、変換が暗黙的に行われる特定の型の関数の呼び出しを明示的に禁止することです。

struct sometype {
    sometype() = delete; // OK, but redundant
    some_type(std::intmax_t) = delete;
    some_type(double);
};

上記の例は明確で目的があります。ただし、新しい演算子がオーバーライドされ、削除済みとして定義することで呼び出されないようにする次の例では、後で質問セクションで特定する他のシナリオについて考え始めました (以下の例は、C++の §8.4.3 からのものです)。 11 標準ドラフト):

struct sometype {
    void *operator new(std::size_t) = delete;
    void *operator new[](std::size_t) = delete;
};
sometype *p = new sometype; // error, deleted class operator new
sometype *q = new sometype[3]; // error, deleted class operator new[]

質問:

この考えを継承に拡張することで、次の使用例が明確で有効なユースケースなのか、それとも新しく追加された機能の不明確な悪用なのかについて、他の人の考えに興味があります。回答の根拠を示してください (最も説得力のある理由を示す例が採用されます)。次の例では、ライブラリの 2 番目のバージョンが最初のバージョンから継承されるようにすることで、ライブラリの 2 つのバージョンを維持しようとします (ライブラリはインスタンス化する必要があります)。アイデアは、最初のライブラリ バージョンに加えられたバグ修正または変更が自動的に 2 番目のライブラリ バージョンに反映されるようにし、2 番目のライブラリ バージョンが最初のバージョンとの違いのみに集中できるようにすることです。2 番目のライブラリ バージョンで関数を非推奨にするには、

class LibraryVersion1 {
public:
    virtual void doSomething1() { /* does something */ }
    // many other library methods
    virtual void doSomethingN() { /* does something else */ }
};

class LibraryVersion2 : public LibraryVersion1 {
public:
    // Deprecate the doSomething1 method by disallowing it from being called
    virtual void doSomething1() override = delete;

    // Add new method definitions
    virtual void doSomethingElse() { /* does something else */ }
};

このようなアプローチには多くの利点がありますが、機能の悪用であると考える傾向が強いと思います。上記の例に見られる主な落とし穴は、継承の古典的な「is-a」関係が壊れていることです。私は多くの記事を読んだことがありますが、継承を使用して「一種の」関係を表現することを強く推奨し、代わりにラッパー関数を使用して構成を使用してクラスの関係を明確に識別することを強く推奨しています。次のよく眉をひそめられる例では、実装と保守にさらに多くの労力が必要ですが (このコード部分に記述された行数に関しては、パブリックに使用できるすべての継承された関数は、継承クラスによって明示的に呼び出される必要があるため)、上記の削除は、多くの点で非常に似ています。

class LibraryVersion1 {
public:
    virtual void doSomething1() { /* does something */ }
    virtual void doSomething2() { /* does something */ }
    // many other library methods
    virtual void doSomethingN() { /* does something */ }
};

class LibraryVersion2 : private LibraryVersion1 {
    // doSomething1 is inherited privately so other classes cannot call it
public:
    // Explicitly state which functions have not been deprecated
    using LibraryVersion1::doSomething2();
    //  ... using (many other library methods)
    using LibraryVersion1::doSomethingN();

    // Add new method definitions
    virtual void doSomethingElse() { /* does something else */ }
};

この潜在的な削除のユースケースについての回答とさらなる洞察を前もって感謝します。

4

3 に答える 3

11

C++ 標準のパラグラフ 8.4.3/2 は、仮想関数をオーバーライドする関数を削除することを間接的に禁止しています。

「宣言する以外に、削除された関数を暗黙的または明示的に参照するプログラムは、形式が正しくありません。[注:これには、関数を暗黙的または明示的に呼び出し、関数へのポインターまたはメンバーへのポインターを形成することが含まれます」

基本クラスへのポインターを介してオーバーライド仮想関数を呼び出すことは、関数を暗黙的に呼び出す試みです。したがって、8.4.3/2 に従って、これを許可する設計は違法です。また、オーバーライドする仮想関数を削除できる C++11 準拠のコンパイラがないことにも注意してください。

より明示的には、同じことがパラグラフ 10.3/16 によって義務付けられています。

「定義が削除された関数 (8.4) は、定義が削除されていない関数をオーバーライドしてはなりません。同様に、定義が削除されていない関数は、定義が削除された関数をオーバーライドしてはなりません。

于 2013-01-16T19:25:17.947 に答える
6

10.3p16:

定義が削除された関数 (8.4) は、定義が削除されていない関数をオーバーライドしてはなりません。同様に、定義が削除されていない関数は、定義が削除された関数をオーバーライドしてはなりません。

他の回答はその理由をよく説明していますが、公式の Thou Shalt Not があります。

于 2013-01-16T20:08:48.140 に答える
3

いくつかの関数を考えてみましょう:

void f(LibraryVersion1* p)
{
    p->doSomething1();
}

これは、LibraryVersion2 が書き込まれる前にコンパイルされます。

したがって、削除された仮想を使用して LibraryVersion2 を実装します。

f はすでにコンパイルされています。どの LibraryVersion1 サブクラスで呼び出されたかは、実行時までわかりません。

これが、削除されたバーチャルが合法でなく、意味がない理由です。

あなたができる最善のことは次のとおりです。

class LibraryVersion2 : public LibraryVersion1
{
public:
    virtual void doSomething1() override
    {
         throw DeletedFunctionException();
    }
}
于 2013-01-16T19:25:08.913 に答える