【发布时间】:2025-12-23 07:40:16
【问题描述】:
我有模板类ItemContainer,它实际上是整个系列容器的外观,具有不同的功能,如排序、索引、分组等。
使用 pimpl idiom 和显式实例化将实现细节隐藏在 cpp. 文件中。模板仅使用众所周知的有限实现类集来实例化,这些实现类定义了容器的实际行为。
主模板实现了所有容器支持的常用功能 - 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
【问题讨论】:
-
您要解决的问题是标准 C++ 将算法与数据分离的方式无法解决的问题?问题似乎是一个接口太大的类。
-
@MarkB 问题在于接口有大约 10 个通用和专用功能,不值得分成 5 个具有 1-2 个功能的接口。我不想增加这样的实体。
标签: c++ templates visual-c++ c++03