50

ご存知のように、一部の言語にはインターフェースの概念があります。これはJavaです:

public interface Testable {
  void test();
}

これを C++ (または C++11) で最もコンパクトな方法でコード ノイズをほとんど発生させずに実現するにはどうすればよいでしょうか? 別の定義を必要としないソリューションをいただければ幸いです(ヘッダーで十分です)。これは非常に単純なアプローチであり、私でさえバグがあると思います ;-)

class Testable {
public:
  virtual void test() = 0;
protected:
  Testable();
  Testable(const Testable& that);
  Testable& operator= (const Testable& that);
  virtual ~Testable();
}

これは始まりにすぎません..そして、私が望むよりも長いです. それを改善する方法は?おそらく、このためだけに作成された std 名前空間のどこかに基本クラスがありますか?

4

5 に答える 5

46

動的 (ランタイム) ポリモーフィズムについては、Non-Virtual-Interface (NVI) イディオムを使用することをお勧めします。このパターンは、インターフェイスを非仮想およびパブリックに、デストラクタを仮想およびパブリックに、実装を純粋な仮想およびプライベートに保持します。

class DynamicInterface
{
public:
    // non-virtual interface
    void fun() { do_fun(); } // equivalent to "this->do_fun()"

    // enable deletion of a Derived* through a Base*
    virtual ~DynamicInterface() = default;    
private:
    // pure virtual implementation
    virtual void do_fun() = 0; 
};

class DynamicImplementation
:
    public DynamicInterface
{
private:
    virtual void do_fun() { /* implementation here */ }
};

動的ポリモーフィズムの優れた点は、インターフェイス基本クラスへのポインターまたは参照が必要な派生クラスを実行時に渡すことができることです。thisランタイム システムは、ポインターを静的基本型から動的派生型に自動的にダウンキャストし、対応する実装を呼び出します (通常は、仮想関数へのポインターを含むテーブルを介して発生します)。

静的 (コンパイル時のポリモーフィズム) の場合は、Curiously Recurring Template Pattern (CRTP) を使用することをお勧めします。動的ポリモーフィズムのベースから派生への自動ダウンキャストを で行う必要があるため、これはかなり複雑static_castです。この静的キャストは、各静的インターフェイスの派生元であるヘルパー クラスで定義できます。

template<typename Derived>
class enable_down_cast
{
private:  
        typedef enable_down_cast Base;    
public:
        Derived const* self() const
        {
                // casting "down" the inheritance hierarchy
                return static_cast<Derived const*>(this);
        }

        Derived* self()
        {
                return static_cast<Derived*>(this);
        }    
protected:
        // disable deletion of Derived* through Base*
        // enable deletion of Base* through Derived*
        ~enable_down_cast() = default; // C++11 only, use ~enable_down_cast() {} in C++98
};

次に、次のように静的インターフェースを定義します。

template<typename Impl>
class StaticInterface
:
    // enable static polymorphism
    public enable_down_cast< Impl >
{
private:
    // dependent name now in scope
    using enable_down_cast< Impl >::self;    
public:
    // interface
    void fun() { self()->do_fun(); }    
protected:
    // disable deletion of Derived* through Base*
    // enable deletion of Base* through Derived*
    ~StaticInterface() = default; // C++11 only, use ~IFooInterface() {} in C++98/03
};

そして最後に、それ自体をパラメーターとしてインターフェースから派生する実装を作成します

class StaticImplementation
:
    public StaticInterface< StaticImplementation > 
{
private:
    // implementation
    friend class StaticInterface< StaticImplementation > ;
    void do_fun() { /* your implementation here */ }
};

これにより、同じインターフェイスの複数の実装を使用できますが、コンパイル時にどの実装を呼び出しているかを知る必要があります。

では、いつどのフォームを使用するのでしょうか? どちらの形式でも、共通のインターフェイスを再利用し、インターフェイス クラス内に事前/事後条件テストを挿入できます。動的ポリモーフィズムの利点は、ランタイムの柔軟性があることですが、その代わりに仮想関数呼び出し (通常はインライン化の機会がほとんどない関数ポインターを介した呼び出し) が必要になります。静的多項式はその鏡です。仮想関数呼び出しのオーバーヘッドはありませんが、欠点は、より多くの定型コードが必要であり、コンパイル時に何を呼び出しているかを知る必要があることです。基本的に効率と柔軟性のトレードオフです。

注:コンパイル時の多項式では、テンプレート パラメーターも使用できます。CRTP イディオムによる静的インターフェースと通常のテンプレート パラメーターの違いは、CRTP タイプのインターフェースは (メンバー関数に基づいて) 明示的であり、テンプレート インターフェースは (有効な式に基づいて) 暗黙的であることです。

于 2013-01-14T18:40:21.780 に答える
41

どうですか:

class Testable
{
public:
    virtual ~Testable() { }
    virtual void test() = 0;
}

C++ では、これは子クラスのコピー可能性に影響しません。これが言っているのは、子が実装する必要があるtestということだけです(これはまさにインターフェースに必要なものです)。このクラスをインスタンス化することはできないため、親インターフェイス型として直接呼び出すことはできないため、暗黙のコンストラクターについて心配する必要はありません。

子クラスがデストラクタを実装することを強制したい場合は、それを純粋にすることもできます (ただし、インターフェイスに実装する必要があります)。

また、ポリモーフィックな破壊が必要ない場合は、代わりにデストラクタを非仮想保護にすることを選択できることに注意してください。

于 2013-01-14T17:41:50.057 に答える
24

deleteScott Meyers (Effective Modern C++) によると: インターフェイス (またはポリモーフィックな基本クラス) を宣言するときは、基本クラスのポインターまたはtypeid参照を介してアクセスされる派生クラス オブジェクトに対する操作の適切な結果のために、仮想デストラクタが必要です。

virtual ~Testable() = default;

ただし、ユーザーが宣言したデストラクタは移動操作の生成を抑制するため、移動操作をサポートするには次を追加する必要があります。

Testable(Testable&&) = default; 
Testable& operator=(Testable&&) = default;

移動操作を宣言すると、コピー操作が無効になり、以下も必要になります。

Testable(const Testable&) = default;
Testable& operator=(const Testable&) = default;

最終結果は次のとおりです。

class Testable 
{
public:
    virtual ~Testable() = default; // make dtor virtual
    Testable(Testable&&) = default;  // support moving
    Testable& operator=(Testable&&) = default;
    Testable(const Testable&) = default; // support copying
    Testable& operator=(const Testable&) = default;

    virtual void test() = 0;

};

別の興味深い記事: The Rule of Zero in C++

于 2015-06-17T08:11:50.360 に答える
12

単語を に置き換えるclassstruct、すべてのメソッドがデフォルトでパブリックになり、1 行節約できます。

とにかく、純粋な仮想メソッドでクラスをインスタンス化できないため、コンストラクターを保護する必要はありません。これは、コピー コンストラクターにも当てはまります。コンパイラによって生成された既定のコンストラクターは、データ メンバーがないため空になり、派生クラスには完全に十分です。

=コンパイラによって生成されたものは確かに間違ったことをするので、演算子について心配するのは正しいです。実際には、あるインターフェイス オブジェクトを別のインターフェイス オブジェクトにコピーすることは意味がないため、誰もそれについて心配することはありません。よくある間違いではありません。

継承可能なクラスのデストラクタは、常にpublic かつ virtual であるか、protected で非 virtual である必要があります。この場合、私はパブリックとバーチャルを好みます。

最終的な結果は、同等の Java よりも 1 行だけ長くなります。

struct Testable {
    virtual void test() = 0;
    virtual ~Testable();
};
于 2013-01-14T19:37:11.750 に答える
7

ポインター、ハンドル、および/またはクラスのすべてのデータメンバーを管理していない場合、クリーンアップを管理する独自のデストラクタがある場合、「3 つのルール」は不要であることに注意してください。また、仮想基本クラスの場合、基本クラスを直接インスタンス化することはできないため、データ メンバーを持たないインターフェイスを定義するだけであれば、コンストラクターを宣言する必要はありません ... コンパイラーデフォルトで十分です。deleteインターフェイス型のポインターを呼び出すことを計画している場合、保持する必要がある唯一の項目は仮想デストラクタです。したがって、実際には、インターフェースは次のように単純になります。

class Testable 
{
    public:
        virtual void test() = 0;  
        virtual ~Testable();
}
于 2013-01-14T17:39:43.237 に答える