我编写了两个不同的容器类,它们具有相同的接口,但使用不同的成员数据和算法来操作其成员。我还有一个模板函数,它接受一个容器并进行一些有用的计算:
class Container1
{
// implementation here
};
class Container2
{
// implementation here
};
template<typename ContainerType>
void my_function(ContainerType const& container, /* other parameters */)
{
// ...
}
令我困扰的是'my_function'应该只接受Container1
或Container2
这一事实,但代码并未表达,因为ContainerType
可以是任何类型。该函数由容器类型模板化,因为无论容器的内部实现是什么,它都会做同样的事情。
我正在考虑一种变体,其中Container1
和Container2
将是模板类的完全特化。然后我可以更具体地讨论my_function
:
template<typename T>
class Container;
// Tags to mark different container types
struct ContainerType1 { };
struct ContainerType2 { };
template<>
class Container<ContainerType1>
{
// implementation
};
template<>
class Container<ContainerType2>
{
// implementation
};
template<typename T>
void my_function(Container<T> const& container, /* other parameters */)
{
}
在第一种情况下,如果'ContainerType'没有my_function
所需的接口,则使用错误模板参数的编译将失败,这不是非常有用的信息。在第二种情况下,如果我提供除Container<ContainerType1>
或Container<ContainerType2>
以外的任何其他内容,我也会收到编译器错误(模板参数扣除失败),但我更喜欢它,因为它提供了关于什么类型的提示模板参数是预期的。
你对此有何看法?这是一个好的设计理念吗?您认为值得更改代码吗?代码中还有许多其他函数,如my_function
,有时候它们期望的模板参数类型并不明显。我还有哪些其他方法可以使my_function
更具体?我知道Boost Concept Check Library的存在。
为了论证,让我们假设我不想通过使用继承和虚函数来解决问题。
如果它与讨论相关,则使用CRTP强制Container1
和Container2
的公共接口。将来可能会有更多的容器类。
答案 0 :(得分:1)
这种问题有一些解决方案。
您的解决方案(将您的类型实施为template
专业化)是一个,但我并不特别喜欢。
另一个是CRTP:
template<typename T>
struct Container {
// optional, but I find it helpeful
T* self() { return static_cast<T*>(this); }
T const* self() const { return static_cast<T const*>(this); }
// common code between every implementation goes here. It accesses itself through self(), never this
};
class ContainerType1: public Container<ContainerType1> {
// more details
};
class ContainerType2: public Container<ContainerType2> {
// more details
};
这是CRTP的核心。
然后:
template<typename T>
void my_function(Container<T> const& container_, /* other parameters */)
{
T const& container = *(container.self());
}
鲍勃是你的叔叔。作为奖励,这提供了放置公共代码的地方。
另一个选项是标记特征类,用于标记您要支持的类型,例如iterator_traits
。
template<typename T>
struct is_container : std::false_type {};
template<>
struct is_container<ContainerType1> : std::true_type {};
template<>
struct is_container<ContainerType2> : std::true_type {};
你甚至可以进行SFINAE样式模式匹配来检测基类型(比如迭代器的工作方式)。
现在,您的方法可以在is_container<T>::value
上进行测试,或者在is_container<T>{}
上进行广告代码调度。
答案 1 :(得分:0)
我认为您的第一个版本是可行的。
在一天结束时,您总是必须选择最佳方法。第二个可能看起来像一个矫枉过正,虽然它得到了重点。
如果您的容器类都有一个共同的功能(让我们说Container1::hasPackage() or Container2::hasPackage()
并且您选择在my_function
内调用它,那么它会立即将您的观点指向它,即调用它的资格是在经历了许多这样的项目之后,你将开始以相反的方式阅读模板 - 从模板定义开始 - 看看需要什么样的属性来限定特定的类。
说完这一切之后,也许你的问题更适合Code Review
One example I created on ideone正在使用您的类,但向name
添加成员变量my_function
。当然可能会有类支持name
,但开发人员也可能会烧几次手指以实现功能背后的想法。