Допустимо ли динамическое преобразование "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<******> : public BaseVector<******>
{
public:
 Vector<******>(const ****** x, const ****** y, const ****** z);
 Vector<******>(const Vector<int> &other);
 ****** norm() const;
};
</int></******></******></******></******></t></t></t></class></t></vector<t></t></t></t></t></t></t></t></t></t,></class>

Я предполагаю, что BaseVector будет не более чем деталью реализации. Это работает, но меня беспокоит operator+=. Мой вопрос: динамический приведение указателя this кодового запаха? Есть ли лучший способ добиться того, что я пытаюсь сделать (избегать дублирования кода и ненужных бросков в коде пользователя)? Или я уверен, поскольку конструктор BaseVector является закрытым?

EDIT:

Извините, да, у меня есть виртуальный dtor, но я забыл вставить его, код не компилируется без него.

5 ответов

Я бы рекомендовал рассмотреть альтернативный подход (обратите внимание, что в приведенных ниже примерах я упростил код до минимального минимума, необходимого для демонстрации альтернативных подходов).

Сначала рассмотрим Curiously Recurring Template Parameter (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="">>
{
};
</t,></typename></derivedvector&></typename>

Поскольку вы всегда знаете, что такое производный тип, достаточно static_cast. Если Vector является единственным классом, базой которого является BaseVector, и если вам гарантировано, что параметры T всегда одинаковы, то, строго говоря, параметр CRTP не нужен: вы всегда знаете, что такое производный тип, поэтому a 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;
}
</t></t></t></typename></t,></typename></typename>

Хотя последнее предпочтительнее для операторов, оно не будет работать для обычных, неоператорных функций-членов, поэтому для них предпочтительнее использовать CRTP-подход.


Да, с технической точки зрения это нормально. Однако для того, чтобы dynamic_cast работал, ваш базовый класс должен быть полиморфным. Поэтому вам нужно сделать хотя бы деструктор (чистый) виртуальный.

Я также хочу добавить, что вместо:

// potential null dereference
return *dynamic_cast<vector<t> * const>(this);
</vector<t>

безопаснее писать:

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

Чтобы ответить на ваш оригинальный вопрос:

Есть ли лучший способ достичь того, что я пытаюсь сделать (избегать дублирования кода и ненужных бросков в коде пользователя)?

Да. Читайте о статическом полиморфизме, любопытно повторяющемся шаблоне шаблонов и разработке класса на основе политик, если вы хотите узнать больше.


Ни один из ваших методов не является virtual. Без каких-либо виртуальных методов компилятор не будет добавлять информацию типа времени выполнения. Без RTTI dynamic_cast не будет работать.


Я думаю, что лучший способ выполнить это идиома pimpl. Как вы говорите, 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<******> : public Vector<type_wrapper<******>>
{
public:
 Vector(const ****** x, const ****** y, const ****** z);
 Vector(const Vector<int> &other);
 ****** norm() const;
};
</int></type_wrapper<******></******></vector<t></t></t></t></wrappedt></typename></class></type_wrapper<t></class></class></class>

Таким образом, вам не нужны лишние классы, за исключением обертки и метафайла типа, которые могут быть повторно использованы.

licensed under cc by-sa 3.0 with attribution.