Страницы

Поиск по вопросам

пятница, 27 декабря 2019 г.

Наследование перегруженных арифметических операторов c++

#cpp #ооп #наследование #перегрузка_операторов


Есть шаблонные классы

template< int rows, int columns, typename Type >
class Matrix : public Tensor {...}

template< int size, typename Type >
class Vector : public Tensor {...}

template< typename Type >
class Quaternion : public Tensor<4, Type> {...}


Которые все унаследованы от шаблонного базового класса:

template< int elements, typename Type = DEFUALT_TYPE >
class Tensor {
  Tensor() {...}
  Tensor( const Tensor& other ) {...}
 ~Tensor() {...}

  template< int elements, typename Type, typename Scalar >
  friend Tensor< elements, Type> operator+( const Tensor< elements, Type>& left,
const Scalar right );
  ...
  // overloading operators -,*,/,+=,-=,*=,/=
protected:
  Type array[elements];
}


Который, в основном, отвечает за базовые арифметические операции: сложение, вычитание,
умножение, деление на скаляр, так как для всех наследованных типов эти операции одинаковы.

Но проблема в том, что эти операторы возвращают базовый класс Tensor, что, в свою
очередь, порождает некоторые проблемы:


Нельзя присвоить результат сложение вектора и скаляра вектору:

Vector<3,double> vec1;
Vector<3,doubel> vec2 = vec1+1;

Для классов Matrix, Vector, Quaternion перегружен оператор стандартного вывода, но
такое не сработает:

std::cout << vec1+2;

Нельзя сразу же использовать результат сложения:

(vec1+5).length();



Хотя, это почти то же самое что и 2 пункт.

Первую проблему можно решить, например, создав для Matrix, Vector, Quaternion конструктор
копирования, который бы создавал объект из объекта класса Tensor:

Vector( const Tensor& tensor ) : Tensor( tensor ) {...}


Но это не решает остальных проблем, и пропускает такой, заведомо ложный код:

Matrix<2,2,double> mat1;
Vector<4,double> vec1 = mat1+2;


Сейчас я решил проблему тем, что для каждого наследуемого класса я определяю эти
операторы заново. Но это во-первых муторно, ведь для каждого класса их примерно 12
штук. А если надо что-то поменять? Добавить новый класс? А во-вторых это дублирование
идентичного кода.

Как можно разрешить эту проблему поэлегантнее?
    


Ответы

Ответ 1



Можно воспользоваться техникой известной под именем CRTP. Приведу пример, как Вам можно это реализовать, а Вы уже сами заточите под свой код: #include template class Base { public: Derived operator+(int scalar) { //добавим сюда сложение return *self(); } private: Derived* self() { return static_cast(this); } }; template class A: public Base> { public: A() = default; A(const A&) { std::cout << "A copy ctor\n"; } }; template class B: public Base> { public: B() = default; B(const B&) { std::cout << "B copy ctor\n"; } }; int main(int argc, char* argv[]) { A a; B b; a = a + 1; b = b + 5; // Следующие строки не скомпилируются //a = b + 1; //b = a + 1; }

Ответ 2



Сразу скажу, что ответ не претендует на звание максимально полного. Имхо, лучший ответ стоит искать в реализации популярных математических библиотек. Но для решения обозначенных проблем могу предложить следующие решения: Для запрета неявного преобразования Tensor в Vector достаточно сделать соответствующий конструктор явным: explicit Vector(const Tensor& tensor); Для реализации бинарных операторов типа operator+ всё таки придется определеять его для каждого нового класса. Необходимость этого вытекает из-за возвращения нового свежесозданного объекта (а не ссылки на существующий). При этом код оператора каждого нового класса вполне может использовать вызов оператора базового класса, уменьшая таким образом дублирование кода. В качестве демонстрации прилагаю код с иерархией из двух классов. Параллели с тензорами, матрицами и векторами попробуйте провести сами. #include struct B { B(int i): i{i} {} int i; }; B operator+(const B& lhs, const B& rhs) { return B(lhs.i + rhs.i); } struct D : B { explicit D(int i, double j=0) : B{i}, j{j} {} explicit D(const B& b, double j=0): B{b}, j{j} {} double j; }; D operator+(const D& lhs, const D& rhs) { return D(static_cast(lhs) + static_cast(rhs), lhs.j + rhs.j); } D operator+(const D& lhs, const B& rhs) { return D(static_cast(lhs) + static_cast(rhs), lhs.j); } D operator+(const B& lhs, const D& rhs) { return D(static_cast(lhs) + static_cast(rhs), rhs.j); } std::ostream& operator<<(std::ostream& o, const B& b) { return o << b.i; } std::ostream& operator<<(std::ostream& o, const D& d) { return o << static_cast(d) << ' ' << d.j; } int main() { D d{1, 2.25}; B b{5}; std::cout << d + 2 << '\n'; std::cout << 2 + d << '\n'; std::cout << b + d << '\n'; std::cout << d + b << '\n'; std::cout << b + 3 << '\n'; std::cout << 3 + b << '\n'; std::cout << (d + 10).j << '\n'; } Результаты выполнения

Комментариев нет:

Отправить комментарий