4

这更像是一个设计问题。

我有一个模板类,我想根据模板类型向它添加额外的方法。为了实践 DRY 原则,我提出了这种模式(有意省略了定义):

template <class T>
class BaseVector: public boost::array<T, 3>
{
protected:
    BaseVector<T>(const T x, const T y, const T z);
public:
    bool operator == (const Vector<T> &other) const;
    Vector<T> operator + (const Vector<T> &other) const;    
    Vector<T> operator - (const Vector<T> &other) const;
    Vector<T> &operator += (const Vector<T> &other)
    {
        (*this)[0] += other[0];
        (*this)[1] += other[1];
        (*this)[2] += other[2];

        return *dynamic_cast<Vector<T> * const>(this);
    }

    virtual ~BaseVector<T>()
    {
    }
}

template <class T>
class Vector : public BaseVector<T>
{
public:
    Vector<T>(const T x, const T y, const T z)
    : BaseVector<T>(x, y, z)
    {
    }
};

template <>
class Vector<double> : public BaseVector<double>
{
public:
    Vector<double>(const double x, const double y, const double z);
    Vector<double>(const Vector<int> &other);
    double norm() const;
};

我打算 BaseVector 只不过是一个实现细节。这行得通,但我担心operator+=. 我的问题是:this指针的动态转换是代码气味吗?有没有更好的方法来实现我想要做的事情(避免代码重复和用户代码中不必要的强制转换)?或者我是安全的,因为 BaseVector 构造函数是私有的?

编辑:

抱歉,是的,我有虚拟 dtor,但我忘了粘贴它,没有它代码无法编译。

4

5 回答 5

5

我建议您考虑一种替代方法(请注意,在下面的示例中,我已将代码简化为演示替代方法所需的最低限度)。

首先,考虑奇怪重复的模板参数(CRTP):

template <typename T, typename DerivedVector>
struct BaseVector
{
    DerivedVector& operator+=(DerivedVector const& other)
    {
        return static_cast<DerivedVector&>(*this);
    }
};

template <typename T>
struct Vector : BaseVector<T, Vector<T>>
{
};

由于您总是知道派生类型是什么,因此 astatic_cast就足够了。IfVector<T>是唯一的基类,BaseVector<T>并且如果您保证T参数始终相同,那么严格来说,CRTP 参数是不必要的:您始终知道派生类型是什么,因此 astatic_cast是安全的。

或者,考虑运算符不必是成员函数,因此您可以声明非成员运算符函数模板:

template <typename T, typename DerivedVector>
struct BaseVector
{
};

template <typename T>
struct Vector : BaseVector<T, Vector<T>>
{
};

template <typename T>
Vector<T>& operator+=(Vector<T>& self, Vector<T> const& other)
{
    return self;
}

虽然后者更适合操作员,但它不适用于普通的非操作员成员函数,因此 CRTP 方法更适合那些。

于 2012-07-02T21:54:41.553 回答
4

是的,从技术角度来看是可以的。但是,为了使dynamic_cast您的基类能够工作,您的基类需要是多态的。因此,您至少需要将析构函数(纯)虚拟化。

我还想添加它而不是:

// potential null dereference
return *dynamic_cast<Vector<T> * const>(this);

写起来更安全:

// potential std::bad_cast exception
return dynamic_cast<Vector<T> & const>(*this);

要回答您的原始问题:

有没有更好的方法来实现我想要做的事情(避免代码重复和用户代码中不必要的强制转换)?

是的。如果您想了解更多信息,请阅读静态多态性奇怪重复的模板模式基于策略的类设计。

于 2012-07-02T21:13:37.003 回答
3

你的方法都不是virtual. 如果没有任何虚方法,编译器将不会添加任何运行时类型信息。没有 RTTI,dynamic_cast将无法工作。

于 2012-07-02T21:13:05.647 回答
1

我认为实现这一点的更好方法是pimpl idiom。正如您所说, BaseVector 只是一个实现细节。因此,您班级的客户应该不知道它(这使您可以自由更改它)。

在这种情况下,这将需要Vector 包含一个 BaseVector 而不是从它继承。然后它会定义自己的算术赋值运算符,然后转发给 BaseVector。

这并不违反 DRY,因为仍然只有一个版本的实现细节,它们位于BaseVector. 重复界面非常好。

于 2012-07-02T21:19:08.253 回答
0

这是一个老问题,但我最近不得不解决同样的问题,然后碰巧偶然发现了这个问题,所以我想我会回答。

我使用了一个类型包装器,以便让专业化“从自身派生”:

template <class T>
struct type_wrapper;

template <class T>
struct unwrap_type
{
    typedef T type;
};

template <class T>
struct unwrap_type<type_wrapper<T>>
{
    typedef T type;
};

template <class WrappedT>
class Vector: public boost::array<typename unwrap_type<WrappedT>::type, 3>
{
    typedef typename unwrap_type<WrappedT>::type T;
protected:
    Vector(const T x, const T y, const T z);
public:
    bool operator == (const Vector &other) const;
    Vector<T> operator + (const Vector &other) const;    
    Vector<T> operator - (const Vector &other) const;
    Vector<T> &operator += (const Vector &other)
    {
        (*this)[0] += other[0];
        (*this)[1] += other[1];
        (*this)[2] += other[2];

        return static_cast<Vector<T> &>(*this);
    }
}

template <>
class Vector<double> : public Vector<type_wrapper<double>>
{
public:
    Vector(const double x, const double y, const double z);
    Vector(const Vector<int> &other);
    double norm() const;
};

这样,您就不需要任何多余的类——除了类型包装器和元函数,它们是可重用的。

于 2014-09-15T17:32:19.570 回答