动态转换“this”作为返回值是否可以?

时间:2012-07-02 21:05:42

标签: c++ templates design-patterns this dynamic-cast

这更像是一个设计问题。

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

5 个答案:

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

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