3

ItemContainerソート、インデックス作成、グループ化などのさまざまな機能を備えたコンテナのファミリ全体のファサードであるテンプレートクラスがあります。

実装の詳細は、cpp.pimplイディオムと明示的なインスタンス化を使用してファイルに隠されています。テンプレートは、コンテナの実際の動作を定義する、よく知られた限定された実装クラスのセットでのみインスタンス化されます。

メインテンプレートは、すべてのコンテナでサポートされている共通の機能を実装しています- IsEmpty()、など。GetCount()Clear()

それぞれの特定のコンテナは、それによってのみサポートされるいくつかの機能を専門としています。たとえばSort()、ソートされたコンテナ、operator[Key&]キーインデックス付きコンテナなどです。

そのようなデザインの理由は、クラスが90年代初頭にいくつかの先史時代によって書かれたいくつかのレガシーの手作り自転車コンテナの代わりになるためです。アイデアは、古い腐敗した実装を最新のSTL&Boostコンテナに置き換え、古いインターフェイスを可能な限りそのままにしておくことです。

問題

このような設計は、ユーザーがサポートされていない関数を特定の専門分野から呼び出そうとすると、不快な状況につながります。コンパイルはOKですが、リンクステージでエラーが発生します(シンボルが定義されていません)。あまりユーザーフレンドリーな動作ではありません。

例:

 SortedItemContainer sc;
 sc.IsEmpty(); // OK
 sc.Sort(); // OK

 IndexedItemContainer ic;
 ic.IsEmpty(); // OK
 ic.Sort(); // Compiles OK, but linking fails

もちろん、特殊化の代わりに継承を使用することで完全に回避することはできますが、1〜3個の関数を持つクラスをたくさん作成するのは好きではありません。オリジナルのデザインを維持したい。

リンクステージ1ではなくコンパイルステージエラーになる可能性はありますか?どういうわけか静的アサーションが使える気がします。

このコードのターゲットコンパイラはVS2008であるため、実用的なソリューションはC ++ 03互換である必要があり、MS固有の機能を使用できます。ただし、ポータブルC++11ソリューションも歓迎します。

ソースコード:

// ItemContainer.h
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

template <class Impl> class ItemContainer
{
public:

   // Common functions supported by all specializations
   void Clear();
   bool IsEmpty() const;
   ...

   // Functions supported by sequenced specializations only
   ItemPtr operator[](size_t i_index) const; 
   ...

   // Functions supported by indexed specializations only
   ItemPtr operator[](const PrimaryKey& i_key) const;
   ...

   // Functions supported by sorted specializations only
   void Sort();
   ...

private:

   boost::scoped_ptr<Impl> m_data; ///< Internal container implementation

}; // class ItemContainer

// Forward declarations for pimpl classes, they are defined in ItemContainer.cpp
struct SequencedImpl;
struct IndexedImpl;
struct SortedImpl;

// Typedefs for specializations that are explicitly instantiated
typedef ItemContainer<SequencedImpl> SequencedItemContainer;
typedef ItemContainer<IndexedImpl> IndexedItemContainer;
typedef ItemContainer<SortedImpl> SortedItemContainer;

// ItemContainer.cpp
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Implementation classes definition, skipped as non-relevant
struct SequencedImpl { ... };
struct IndexedImpl { ... };
struct SortedImpl { ... };

// Explicit instantiation of members of SequencedItemContainer
template  void SequencedItemContainer::Clear(); // Common
template  bool SequencedItemContainer::IsEmpty() const; // Common
template  ItemPtr SequencedItemContainer::operator[](size_t i_index) const; // Specific

// Explicit instantiation of members of IndexedItemContainer
template  void IndexedItemContainer::Clear(); // Common
template  bool IndexedItemContainer::IsEmpty() const; // Common
template  ItemPtr IndexedItemContainer::operator[](const PrimaryKey& i_key) const; // Specific

// Explicit instantiation of members of SortedItemContainer
template  void SortedItemContainer::Clear(); // Common
template  bool SortedItemContainer::IsEmpty() const; // Common
template  void SortedItemContainer::Sort(); // Specific

// Common functions are implemented as main template members
template <class Impl> bool ItemContainer<Impl>::IsEmpty() const
{
   return m_data->empty(); // Just sample
}

// Specialized functions are implemented as specialized members (partial specialization)
template <> void SortedItemContaner::Sort()
{
   std::sort(m_data.begin(), m_data.end(), SortFunctor()); // Just sample
}

...
// etc
4

4 に答える 4

4

特定の関数が実装されないことがコンパイル時にわかっている場合、その関数は最初から宣言されるべきではありませんでした。それ以外の場合、これはプログラミング エラーです。

そうは言っても、そのような関数の宣言を避けるか、実装されている場合にのみ宣言が機能するように宣言する必要があります。これは、static_assertまたは SFINAE によって実現できます。例えば

template<class Container>   // you need one instantination per container supported
struct container_traits
{
   static const bool has_sort;  // define appropriately in instantinations
   /* etc */
};

template<class container>
class ContainerWrapper {

  unique_ptr<container> _m_container;

  template<bool sorting> typename std::enable_if< sorting>::type
  _m_sort()
  {
    _m_container->sort();
  }

  template<bool sorting> typename std::enable_if<!sorting>::type
  _m_sort()
  {
    static_assert(0,"sort not supported");
  }

public

  void sort()
  {
    _m_sort<container_traits<container>::has_sort>();
  }

  /* etc */

};
于 2012-09-27T21:31:18.887 に答える
3

次の例を検討してください。

class A {
public:
  void foo() {}
  void bar();
};

リンク フェーズ中にのみ、A::bar()定義されていないエラーが検出される可能性があり、これはテンプレートとは関係ありません。

コンテナごとに個別のインターフェースを定義し、それらを実装に使用する必要があります。以下の可能性の 1 つにすぎません。

template <class Impl> 
class ItemContainerImpl
{
public:
   ItemContainerImpl();
protected:
   boost::scoped_ptr<Impl> m_data; ///< Internal container implementation
};

// No operations
template <class Impl>
class Empty : protected virtual ItemContainerImpl<Impl> {};

template <class Impl, template <class> class Access, template <class> class Extra = Empty> 
class ItemContainer : public Extra<Impl>, public Access<Impl>
{
public:

   // Common functions supported by all specializations
   void Clear();
   bool IsEmpty() const;
   ...
};

template <class Impl>
class SequencedSpecialization : protected virtual ItemContainerImpl<Impl> {
public:
   // Functions supported by sequenced specializations only
   ItemPtr operator[](size_t i_index) const; 
   ...
};


template <class Impl>
class IndexedSpecialization : protected virtual ItemContainerImpl<Impl> {
public:
   // Functions supported by indexed specializations only
   ItemPtr operator[](const PrimaryKey& i_key) const;
   ...
};

template <class Impl>
class Sorted : protected virtual ItemContainerImpl<Impl> {
public:
   // Functions supported by sorted specializations only
   void Sort();
   ...
};

// Typedefs for specializations that are explicitly instantiated
typedef ItemContainer<SequencedImpl, SequencedSpecialization> SequencedItemContainer;
typedef ItemContainer<IndexedImpl, IndexedSpecialization> IndexedItemContainer;
typedef ItemContainer<SortedImpl, IndexedSpecialization, Sorted> SortedItemContainer;
于 2012-09-27T20:31:08.983 に答える
2

SFINAEの使用を提案する良い答えにもかかわらず、私は元の設計に適合するソリューションを探し続けました。そしてついに私はそれを見つけました。

重要なアイデアは、明示的なインスタンス化ではなく、特定の関数メンバーに特殊化を使用することです。

何が行われたか:

  1. メインテンプレートに特定の関数のダミー実装を追加しました。静的アサートのみを含む実装はヘッダーファイルに配置されましたが、クラス定義にはインライン化されていませんでした。
  2. 特定の関数の明示的なインスタンス化が.cppファイルから削除されました。
  3. 特定の関数の特殊化宣言がヘッダーファイルに追加されました。

ソースコード:

// ItemContainer.h
//////////////////////////////////////////////////////////////////////////////
template <class Impl> class ItemContainer
{
public:

   // Common functions supported by all specializations
   void Clear();
   bool IsEmpty() const;
   ...

   // Functions supported by sorted specializations only
   void Sort();
   ...

private:

   boost::scoped_ptr<Impl> m_data; ///< Internal container implementation

}; // class ItemContainer

// Dummy implementation of specialized function for main template
template <class Impl> void ItemContainer<Impl>::Sort()
{
   // This function is unsupported in calling specialization
   BOOST_STATIC_ASSERT(false);
}

// Forward declarations for pimpl classes,
// they are defined in ItemContainer.cpp
struct SortedImpl;

// Typedefs for specializations that are explicitly instantiated
typedef ItemContainer<SortedImpl> SortedItemContainer;

// Forward declaration of specialized function member
template<> void CSortedOrderContainer::Sort();

// ItemContainer.cpp
//////////////////////////////////////////////////////////////////////////////

// Implementation classes definition, skipped as non-relevant
struct SortedImpl { ... };

// Explicit instantiation of common members of SortedItemContainer
template  void SortedItemContainer::Clear();
template  bool SortedItemContainer::IsEmpty() const;

// Common functions are implemented as main template members
template <class Impl> bool ItemContainer<Impl>::IsEmpty() const
{
   return m_data->empty(); // Just sample
}

// Specialized functions are implemented as specialized members
// (partial specialization)
template <> void SortedItemContaner::Sort()
{
   std::sort(m_data.begin(), m_data.end(), SortFunctor()); // Just sample
}

...
// etc

このようにして、少なくともVS2008では機能します。

C ++ 11をstatic_assert使用するGCCの場合、レイジーテンプレート関数のインスタンス化を有効にするためのトリックが必要です(コンパイルされたサンプル)。

template <class T> struct X
{
    void f();
};

template<class T> void X<T>::f()
{
   // Could not just use static_assert(false) - it will not compile.
   // sizeof(T) == 0 is calculated only on template instantiation and       
   // doesn't produce immediate compilation error
   static_assert(sizeof(T) == 0, "Not implemented");
}

template<> void X<int>::f()
{
  std::cout << "X<int>::f() called" << std::endl;
}

int main()
{
   X<int> a;
   a.f(); // Compiles OK

   X<double> b;
   b.f(); // Compilation error - Not implemented!
}
于 2012-10-16T20:16:48.880 に答える
1

これはどうですか ?

template <class T, class supported_types> struct vec_enabler : 
  boost::mpl::contains<supported_types, T> {};

// adding Sort interface
template <class T, class enabler, class Enable = void>
struct sort_cap{};

template <class T, class enabler>
struct sort_cap<T, enabler, 
                typename boost::enable_if< typename enabler::type >::type>
{
  void Sort();
};

// adding operator[]
template <class T, class U, class R, class enabler, class Enable = void>
struct index_cap{};

template <class T, class primary_key, class ret, class enabler>
struct index_cap<T, primary_key, ret, enabler, 
                 typename boost::enable_if< typename enabler::type >::type>
{
  ret operator[](primary_key i_index) const;
};


template <class Impl> 
class ItemContainer : 
  public sort_cap<Impl, 
                  vec_enabler<Impl, boost::mpl::vector<A, B> > >, // sort for classes A or B
  public index_cap<Impl, size_t, ItemPtr, 
                   vec_enabler<Impl, boost::mpl::vector<C> > >, // index for class C
  public index_cap<Impl, primaryKey, ItemPtr, 
                   vec_enabler<Impl, boost::mpl::vector<B> > > // index for class B
{
public:
  void Clear();
  bool IsEmpty() const;
}; 

継承を使用することが、やりたいことを達成するための最もクリーンな方法であることがわかりました(これは「クラスへのインターフェースの追加」です)。次に、次のようになります。

int main(){
    ItemContainer<A> cA;
    cA.Sort();

    //ItemPtr p = cA[0]; // compile time error

    ItemContainer<C> cC;
    //cC.Sort(); // compile time error
    ItemPtr p = cC[0];
    //ItemPtr pp= cC[primaryKey()]; // compile time error
}

もちろん、実装を .cpp ファイルに記述することもできます。

于 2012-10-02T10:10:53.043 に答える