4

In C++ an interface can be implemented by a class whose methods are pure virtual.

Such a class could be part of a library to describe what methods an object should implement to be able to work with other classes in the library:

class Lib::IFoo
{
    public:
        virtual void method() = 0;
};

:

class Lib::Bar
{
    public:
        void stuff( Lib::IFoo & );
};

Now I want to to use class Lib::Bar, so I have to implement the IFoo interface.

For my purposes I need a whole of related classes so I would like to work with a base class that guarantees common behavior using the NVI idiom:

class FooBase : public IFoo // implement interface IFoo
{
    public:
        void method(); // calls methodImpl;

    private:
        virtual void methodImpl();
};

The non-virtual interface (NVI) idiom ought to deny derived classes the possibility of overriding the common behavior implemented in FooBase::method(), but since IFoo made it virtual it seems that all derived classes have the opportunity to override the FooBase::method().

If I want to use the NVI idiom, what are my options other than the pImpl idiom already suggested (thanks space-c0wb0y).

4

4 に答える 4

6

NVI パターンが間違っていると思います: http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-Virtual_Interface

ただし、それで問題が解決するかどうかはわかりません。

class IFoo
{
    public:
       void method() { methodImpl(); }
    private:
       virtual void methodImpl()=0;
};

class FooBase : public IFoo // implement interface IFoo
{
    private:
        virtual void methodImpl();
};

XML から読み取るリーダーと DB から読み取るリーダーを使用してこれを行う理由の例を次に示します。一般的な構造は NVI readFromSource に移動され、一般的でない動作はプライベート仮想 getRawDatum に移動されることに注意してください。このように、ログとエラー チェックは 1 つの関数でのみ必要です。

class IReader
{
  public:
    // NVI
    Datum readFromSource()
    {
       Datum datum = getRawDatum();
       if( ! datum.isValid() ) throw ReaderError("Unable to get valid datum");
       logger::log("Datum Read");
       return datum;
    }
  private:
    // Virtual Bits
    Datum getRawDatum()=0;
};

class DBReader : public IReader
{
  private:
    Datum getRawDatum() { ... }
};

class XmlReader : public IReader
{
   private:
     Datum getRawDatum() { ... }
};
于 2010-04-29T07:57:15.957 に答える
4

一般に、NVI (「テンプレート メソッド」とも呼ばれる) を使用する理由は、派生クラスが基本クラスの動作の一部のみを変更する必要があるためです。だからあなたがすることはこれです:

class base {
  public:
    void f()
    {
      // do something derived classes shouldn't interfere with          
      vf();
      // do something derived classes shouldn't interfere with          
      vg();
      // do something derived classes shouldn't interfere with          
      vh();
      // do something derived classes shouldn't interfere with          
    }
  private:
    virtual void vf(); // might be pure virtual, too
    virtual void vg(); // might be pure virtual, too
    virtual void vh(); // might be pure virtual, too
};

派生クラスは、基本的なアルゴリズムを台無しにすることなく、f()意図された場所にプラグインし、 の動作の側面を変更できます。f()

于 2010-04-29T07:56:42.840 に答える
2

virtualメソッドが基底クラスで virtual として宣言されると、キーワードがそこで使用されていなくても、すべての派生クラスで自動的に virtual になるので、混乱するかもしれません。したがって、あなたの例では、両方の方法FooBaseが仮想です。

...派生クラスが FooBase::method() で実装されている一般的な動作をオーバーライドする可能性を否定する...

IFooを取り除き、階層をFooBase非仮想で開始することができれば、それで十分ですmethodIFooしかし、の直接の子をオーバーライドできるようにしたいようですが、のmethod()子がFooBaseそれをオーバーライドできないようにします。それは不可能だと思います。

于 2010-04-29T09:25:13.810 に答える
1

これを実現するには、pimpl-イディオムを使用できます。

class IFoo
{
    public:
        IFoo( boost::shared_ptr< IFooImpl > pImpl )
            : m_pImpl( pImpl )
        {}

        void method() { m_pImpl->method(); }
        void otherMethod() { m_pImpl->otherMethod(); }
    private:
        boost::shared_ptr< IFooImpl > m_pImpl;
};

class IFooImpl
{
    public:
        void method();
        virtual void otherMethod();
};

現在、他の人はサブクラスIFooImpl化して に渡すことIFooができますが、 の動作をオーバーライドすることはできませんmethod(オーバーライドできますotherMethod)。IFooImplの直接サブクラスを作成して、正しく初期化するためにIFoo使用することもできます。これはメソッドの要点です。このアプローチを微調整する方法はたくさんあります。たとえば、factory-patternを使用して、が正しく作成されていることを確認できます。enable_shared_from_thisIFooIFoo

それが役立つことを願っています。

于 2010-04-29T08:53:36.970 に答える