9

考虑以下两种使用场景(如您所见,即最终用户只会对使用Vector2_tand感兴趣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;

我无法决定哪个是更好的解决方案。继承的明显优势是派生类中的代码重用;一个可能的缺点是性能(更大的尺寸,用户可能通过价值等)。专业化似乎可以避免这一切,但代价是我不得不多次重复自己。

我错过了哪些其他优点/缺点,在您看来,我应该走哪条路?

4

4 回答 4

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。它也不会为执行空基类优化(很常见)的编译器增加任何空间开销。

于 2009-04-07T00:13:13.693 回答
4

使用继承和私有继承。并且不要使用任何虚函数。由于使用私有继承,您没有 is-a,因此没有人能够使用指向派生子类的 baseclas 指针,并且在按值传递时不会遇到切片问题。

这为您提供了两全其美的优势(事实上,这也是大多数库实现许多 STL 类的方式)。

来自http://www.hackcraft.net/cpp/templateInheritance/(讨论 std::vector,而不是你的 Vector):

vector<T*>被声明为具有vector<void*>. 所有将新元素放入向量中的函数,例如push_back(),都在这个私有基础上调用等效函数,因此在内部我们vector<T*>使用 a vector<void*>存储。所有从向量返回元素的函数,例如 front(),对调用私有基上的等效函数的结果执行 a static_cast。由于获得指向vector<void*>(除了故意危险的技巧)的指针的唯一方法是通过vector<T*>它提供的接口,因此可以安全地静态地将void*back to T* (或void*&back toT*&等)。

一般来说,如果 STL 这样做,它似乎是一个不错的模仿模型。

于 2009-04-07T00:10:14.777 回答
0

继承只能用于建模“is-a”。专业化将是更清洁的选择。如果您出于某种原因需要或想要使用继承,至少将其设为私有或受保护的继承,这样您就不会从具有公共非虚拟析构函数的类公开继承。

是的,模板元编程的人总是这样做

 struct something : something_else {};

但是那些somethings 是元函数,并不意味着用作类型。

于 2009-07-29T09:05:14.510 回答
-1

如果您过多地使用模板专业化,您可能需要重新考虑您的设计。考虑到你把它藏在 a 后面typedef,我怀疑你需要它。

于 2009-04-07T00:08:53.780 回答