这更像是一个设计问题。
我有一个模板类,我想根据模板类型添加额外的方法。为了实践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,但我忘了粘贴它,代码没有它就编译。
答案 0 :(得分: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);
回答你原来的问题:
有没有更好的方法来实现我想要做的事情(避免代码重复,以及用户代码中不必要的强制转换)?
是。如果您想了解更多信息,请阅读静态多态,好奇地重复模板模式和基于策略的类设计。
答案 1 :(得分:4)
我建议您考虑另一种方法(请注意,在下面的示例中,我已将代码简化为演示替代方法所需的最低限度)。
首先,考虑奇怪的重复模板参数(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>>
{
};
由于您始终知道派生类型是什么,static_cast
就足够了。如果Vector<T>
是唯一一个基数为BaseVector<T>
的类,并且如果保证T
参数始终相同,那么严格来说,CRTP参数是不必要的:你总是知道什么派生类型是,所以static_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方法更适合那些人。
答案 2 :(得分:3)
您的所有方法都不是virtual
。如果没有任何虚方法,编译器将不会添加任何运行时类型信息。如果没有RTTI,dynamic_cast
将无效。
答案 3 :(得分:1)
我认为实现这一目标的更好方法是pimpl idiom。如你所说,BaseVector只是一个实现细节。因此,您班级的客户应该不了解它(您可以自由更改它)。
在这种情况下,这需要让Vector
包含一个BaseVector,而不是继承它。然后它将定义自己的算术赋值运算符,它将转发给BaseVector。
这不违反DRY,因为仍然只有一个版本的实施细节,它们位于BaseVector
。重复界面非常好。
答案 4 :(得分:0)
这是一个老问题,但我最近不得不解决同样的问题然后偶然发现了这个问题,所以我想我会回答。
我使用了一个类型包装器,以便从自身中获得专业化&#39;:
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;
};
这样,您不需要任何冗余类 - 除了可重用的类型包装器和元函数。