考虑以下两种使用方案(完全如您所见,也就是说,最终用户只对使用Vector2_t
和Vector3_t
感兴趣):
[1]继承:
template<typename T, size_t N> struct VectorBase
{
};
template<typename T> struct Vector2 : VectorBase<T, 2>
{
};
template<typename T> struct Vector3 : VectorBase<T, 3>
{
};
typedef Vector2<float> Vector2_t;
typedef Vector3<float> Vector3_t;
[2]专业化:
template<typename T, size_t N> struct Vector
{
};
template<typename T> struct Vector<T, 2>
{
};
template<typename T> struct Vector<T, 3>
{
};
typedef Vector<float, 2> Vector2_t;
typedef Vector<float, 3> Vector3_t;
我无法决定哪个是更好的解决方案。 继承的明显优势是派生类中的代码重用;可能的缺点是性能(更大的尺寸,用户可以通过价值等)。 专业化似乎避免了这一切,但代价是我不得不多次重复自己。
我错过了哪些其他优点/缺点,并且您认为我应该选择哪条路线?
答案 0 :(得分:12)
我认为,您最终想要的是让用户输入
Vector<T, N>
根据N
,用户会得到一些不同的东西。第一个不会实现这一点,但第二个将是代码重复的代价。
你可以做的是颠倒继承:
template<typename T, size_t N> struct VectorBase
{
};
template<typename T> struct VectorBase<T, 2>
{
};
template<typename T> struct VectorBase<T, 3>
{
};
template<typename T, size_t N> struct Vector : VectorBase<T, N>
{
};
实现少数仅依赖于N在适当的基类中具有某些特定值的函数。您可以在其中添加受保护的析构函数,以防止用户通过指向Vector
的指针删除VectorBase
的实例(通常他们甚至不能命名VectorBase
:将这些基础放在某些实现中名称空间,如detail
)。
另一个想法是将此解决方案与另一个答案中提到的解决方案结合起来。私有地继承(而不是如上所述)并将包装函数添加到调用基类实现的派生类中。
另一个想法是只使用一个类,然后使用enable_if
(使用boost::enable_if
)为N
的特定值启用或禁用它们,或者使用int-to-type像这样的变压器更加简单
struct anyi { };
template<size_t N> struct i2t : anyi { };
template<typename T, size_t N> struct Vector
{
// forward to the "real" function
void some_special_function() { some_special_function(i2t<N>()); }
private:
// case for N == 2
void some_special_function(i2t<2>) {
...
}
// case for N == 3
void some_special_function(i2t<3>) {
...
}
// general case
void some_special_function(anyi) {
...
}
};
这样,它对Vector
的用户完全透明。它也不会为执行空基类优化的编译器增加任何空间开销(很常见)。
答案 1 :(得分:4)
使用继承和私有继承。并且不要使用任何虚拟功能。由于私有继承,你没有is-a,没有人能够使用指向派生子类的baseclas指针,并且当passinfg by value时你不会得到切片问题。
这为您提供了两全其美(实际上大多数库实现了许多STL类)。
来自http://www.hackcraft.net/cpp/templateInheritance/(讨论std :: vector,而不是你的 V ector类):
宣布
vector<T*>
有一个vector<void*>
的私人基地。 所有放置新元素的函数 进入向量,例如push_back()
,调用 这个私人基地的等效功能, 所以内部我们的vector<T*>
正在使用vector<void*>
用于存储。所有功能 从向量返回一个元素,例如front()
,在static_cast
上执行vector<void*>
调用等效函数的结果 在私人基地。既然是获得的唯一途径 指向vector<T*>
的指针(除了 故意危险的伎俩)是通过void*
提供的界面是安全的 静态地将T*
强制转换回void*&
(或T*&
返回{{1}},依此类推。)
一般来说,如果STL这样做,它似乎是一个模拟的合适模型。
答案 2 :(得分:0)
如果您过多地使用模板专业化,则可能需要重新考虑您的设计。考虑到你将它隐藏在typedef
后面,我怀疑你是否需要它。
答案 3 :(得分:0)
继承只应用于模拟“is-a”。专业化将是更清洁的选择。如果您因任何原因需要或想要使用继承,至少要使其成为私有或受保护的继承,因此您不会公开从具有公共非虚拟析构函数的类继承。
是的,模板元编程人员总是这样做
struct something : something_else {};
但那些something
是元函数,并不打算用作类型。