【问题标题】:Inheritance vs Specialization继承与专业化
【发布时间】:2009-04-07 00:03:41
【问题描述】:

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

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

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

【问题讨论】:

    标签: c++ inheritance specialization


    【解决方案1】:

    我认为你最终想要的是让用户输入

    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 是适当基类中的某个特定值的函数。您可以在其中添加一个受保护的析构函数,以防止用户通过指向VectorBase 的指针删除Vector 的实例(通常他们甚至不能命名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。我怀疑您在单个类模板中对 enable_if 的建议是最好的方法——当然,不同向量维度的模板类之间的唯一区别是构造函数中的参数数量?
    • 是的,enable_if 将完全“零成本”:) 现在希望消除那些 i2t 对象。尽管它们应该很容易优化。一个小的表达式模板重载 op,可以用来为 ctor 提供初始化元素。或者使用 op+= 像 boost::assign 一样。
    • 我想也许他想添加诸如叉积之类的东西,他只为 N==3 之类的 N 添加,他可以为此使用 some_special_function(i2t) 或 enable_if
    【解决方案2】:

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

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

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

    vector&lt;T*&gt; 被声明为具有 vector&lt;void*&gt; 的私​​人基地。 放置新元素的所有功能 进入向量,如push_back(),调用 在这个私有基础上的等效功能, 所以在内部我们的vector&lt;T*&gt; 正在使用 vector&lt;void*&gt; 用于存储。所有功能 从向量中返回一个元素,例如 front(),执行static_cast 调用等效函数的结果 在私人基地。因为只有这样才能获得 指向vector&lt;void*&gt; 的指针(除了 故意危险的伎俩)是通过 vector&lt;T*&gt;提供的接口是安全的 将void* 静态转换回T* (或将void*&amp;T*&amp;,依此类推)。

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

    【讨论】:

    • 挑剔:严格来说,您确实有“is a”,但“is a”并不公开。例如。 std:vector&lt;int*&gt;std:vector&lt;void*&gt; 作为实现具有良好空间效率的事物的一种手段。但是,就外部的任何代码而言,“是”私有的,std:vectory&lt;int*&gt; 不是std:vectory&lt;void*&gt;,因为它无法“看到”这种关系。所以严格来说你确实有 is-a,但实际上你没有:)
    【解决方案3】:

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

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

     struct something : something_else {};
    

    但那些somethings 是元函数,并不打算用作类型。

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 2017-01-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-12-17
        • 1970-01-01
        • 2013-07-23
        • 2018-03-17
        相关资源
        最近更新 更多