我有一个模板类ItemContainer
,它实际上是整个系列容器的外观,具有不同的功能,如排序、索引、分组等。
cpp.
使用 pimpl idiom 和显式实例化将实现细节隐藏在文件中。模板仅使用定义容器实际行为的众所周知的有限实现类集进行实例化。
主模板实现了所有容器支持的常用功能 - IsEmpty()
,GetCount()
等Clear()
。
每个特定容器都专门化了一些仅由它支持的功能,例如Sort()
排序容器、operator[Key&]
键索引容器等。
这种设计的原因是该类替代了一些史前人在 90 世纪初编写的几个遗留的手工自行车容器。想法是用现代 STL&Boost 容器替换旧的腐烂实现,尽可能保持旧界面不变。
问题
当用户试图从某些专业中调用不支持的功能时,这种设计会导致不愉快的情况。它编译正常,但在链接阶段产生错误(符号未定义)。不是非常用户友好的行为。
例子:
SortedItemContainer sc;
sc.IsEmpty(); // OK
sc.Sort(); // OK
IndexedItemContainer ic;
ic.IsEmpty(); // OK
ic.Sort(); // Compiles OK, but linking fails
当然,可以通过使用继承而不是专门化来完全避免这种情况,但我不喜欢生成很多具有 1-3 个函数的类。想保留原创设计。
是否有可能将其变成编译阶段错误而不是链接阶段一?我有一种感觉可以以某种方式使用静态断言。
此代码的目标编译器是 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