是否可以将动态强制转换"this"作为返回值?



这更像是一个设计问题。

我有一个模板类,我想根据模板类型向其添加额外的方法。为了实践 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,但我忘了粘贴它,没有它代码无法编译。

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

首先,考虑奇怪的重复模板参数 (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 方法更适合这些函数。

是的,从技术角度来看,这是可以的。但是,为了使dynamic_cast正常工作,基类必须是多态的。所以你至少需要使析构函数(纯)虚拟。

我还想添加它,而不是:

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

写起来更安全:

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

要回答您的原始问题:

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

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

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

我认为实现

这一点的更好方法是痘痘成语。 正如你所说,BaseVector只是一个实现细节。 因此,您类的客户端应该不知道它(这使您可以自由更改它)。

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

这并不违反 DRY,因为仍然只有一个版本的实现细节,并且它们驻留在 BaseVector . 重复界面是完全可以的。

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

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

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;
};

这样,您就不需要任何冗余类 - 除了可重用的类型包装器和元函数。

最新更新