1

例:

template <typename TDerived>
struct Base
{
    void DoSomething()
    {
        if (static_cast<TDerived*>(this)->CheckSomeFlag())
        {
            std::cout << "true" << std::endl;
        }
        else
        {
            std::cout << "false" << std::endl;
        }
    }

    // Default implementation
    bool CheckSomeFlag()
    {
        return false;
    }
};

struct Derived : public Base<Derived>
{
    bool CheckSomeflag()
    {
        return true;
    }
};

int main(int argc, char** argv)
{
    Derived d;

    d.DoSomething();
    return 0;
}

この場合、プログラマーが Derived::CheckSomeFlag() の宣言にタイプミスがあり、F が小文字であるため、プログラムは "false" を出力します。に展開される OVERRIDE(x) のようなマクロを使用して、コンパイル時にこのような状況をキャッチしたいと思いますが、static_assert(is_member_of<Base, x>::value, "error");これを達成するために必要なテンプレートのトリックがわかりません。

4

4 に答える 4

2

テンプレートとマクロを組み合わせると、次のことが可能になります。

  • 同様の関数が基本クラスで宣言されていない場合は、静的にアサートします。
  • 派生クラス内で関数を宣言します。

このアプローチでは、マクロは、基本クラスの特定のメンバー関数の存在をテストする型特性テンプレートを定義します。基本クラスに指定されたメンバー関数がある場合、特性のvalueメンバーは になりますtrue。次にvalue、型特性の が静的アサート内で使用されます。

#define TRAITNAME_HELPER( PREFIX, FN, LN )  PREFIX ## FN ## LN
#define TRAITNAME( FN, LN ) TRAITNAME_HELPER( has_fn, FN, LN )

#define OVERRIDE_IMPL( CLASS, RETURN_TYPE, FN, ARG_TYPES, LN )  \
  /* Type trait used to determine if                            \
   * RETURN_TYPE T::FN( ARG_TYPES ) exists. */                  \
  template < typename T >                                       \
  struct TRAITNAME( FN, LN )                                    \
  {                                                             \
    /* Type that expects a value for the specific type.  For    \
     * example, type_check< int, 4 >. */                        \
    template < typename U, U > struct type_check {};            \
                                                                \
    /* Use type_check expect a specific                         \
     * pointer-to-member-function on T. */                      \
    template < typename U >                                     \
    static std::true_type                                       \
    check( type_check< RETURN_TYPE (T::*)ARG_TYPES,             \
                       &U::FN >* = 0 );                         \
                                                                \
    template < typename U >                                     \
    static std::false_type check( ... );                        \
                                                                \
    /* Determine which check function was resolved for T. */    \
    typedef decltype( check< T >( 0 ) ) type;                   \
    static constexpr decltype(type::value) value = type::value; \
  };                                                            \
  static_assert( TRAITNAME( FN, LN )< CLASS >::value,           \
                 "" #RETURN_TYPE " " #FN #ARG_TYPES             \
                 " is not defined in " #CLASS "." );            \
  RETURN_TYPE FN ARG_TYPES
#define OVERRIDE( CLASS, RETURN_TYPE, FN, ARG_TYPES )           \
  OVERRIDE_IMPL( CLASS, RETURN_TYPE, FN, ARG_TYPES, __LINE__ )
  • OVERRIDE_IMPL マクロの末尾にセミコロンがないため、メンバー関数をクラス内で宣言または定義できます。
  • オーバーロードされたメソッドをサポートするには、追加レベルのマクロが必要です。__LINE__ユニークな型特性を作成するために使用しています。

次のBaseクラスで:

template < typename TDerived >
struct Base
{
  bool CheckSomeFlag();
  bool CheckSomeFlag(int, int);
};

Derviedクラスが次のように定義されている場合:

struct Derived : public Base< Derived >
{
  OVERRIDE( Base< Derived >, bool, CheckSomeflag, () );
};

その後、コンパイルは次のエラー ( demo )で失敗します。

error: static assertion failed: "bool CheckSomeflag() is not defined in Base< Derived >."

ただし、型が正しい場合は、次のようにコンパイルされます

struct Derived : public Base< Derived >
{
  OVERRIDE( Base< Derived >, bool, CheckSomeFlag, () );
  OVERRIDE( Base< Derived >, bool, CheckSomeFlag, (int a, int b) )
  {
    return ( a > b );
  }
};
bool Derived::CheckSomeFlag() { return true; }

このアプローチには、次の 2 つの欠点があります。

  • 戻り値の型は完全に一致する必要があります。余分な作業をしなくても、これにより、共変の戻り値の型が使用されなくなります。ただし、これは CRTP パターンでは望ましい動作である可能性があります。
  • マクロ構文は、関数の型を覆い隠します。たとえば、 a をbool(int,int)返し、bool2 つのint引数を持つ関数の型として使用できる代わりに、マクロに 2 つの別個の引数として渡す必要があります( ..., bool, ..., (int, int) )。これは、マクロが期待する引数の順序を変更することで軽減できますが、関数の通常の宣言に一致する順序を選択します: return-type identifier(args).
于 2012-07-15T21:29:25.640 に答える
1

C++11 でのアイデアは次のとおりです。

#include <iostream>
#include <type_traits>

struct Foo { void f() { } };
struct Bar : Foo { void f() { } };
struct Zip : Foo {              };

int main()
{
    std::cout << "Bar: " << std::is_same<decltype(&Bar::f), void(Foo::*)()>::value << std::endl
              << "Zip: " << std::is_same<decltype(&Zip::f), void(Foo::*)()>::value << std::endl
    ;
}

それはおそらく静的アサートに変わる可能性があります。

于 2012-07-15T15:59:55.527 に答える
0

これにより、コンパイル時エラーが発生するようです。

...
template <bool (TDerived::*TPointer)()> class Checker { };

void DoSomething()
{
    static Checker<&TDerived::CheckSomeFlag> foo;
...

(残念ながら、私はテンプレートの専門家ではありませんが)TDerivedのCheckSomeFlagメンバーにアクセスしようとしているだけで、継承のみが機能するため、コンパイラーはそれをBaseメンバーへのポインターに置き換えることができません。逆の方法(またはまったくないかもしれませんが、非仮想メソッドについてはよくわかりません)

于 2012-07-15T18:00:03.967 に答える
0

コードからこれを行うことはできません。一般的な (そしてそれほど一般的ではない) プログラマーのエラーを検出するために使用できる静的分析ツールを検討することをお勧めします。それらはたくさんあります。一例: http://www.coverity.com/products/static-analysis.html

これは一般的なプログラマ エラーであり、基本クラスの作成者はこれを認識する必要があります。デフォルトの実装を提供したいが、間違いを避けるために派生クラスのプログラマーにメソッドをオーバーライドさせたい場合は、純粋な仮想宣言を使用して次のトリックを使用できます。

class Base {
  public:
  virtual void foo() = 0;
};
// Did you know that you can still implement pure virtual functions? Neat!
void Base::foo() {
  // does something useful that might be applicable to most cases
}
class Derived : public Base {
  typedef Base superclass;
  public:
  void foo() {
    // I am forced to override foo
    // But author of Base kindly provided a default implementation
    superclass::foo();
  }
};
于 2012-07-15T15:20:32.563 に答える