#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. Приведу пример, как Вам можно это реализовать, а Вы уже сами заточите под свой код: #includetemplate 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'; } Результаты выполнения
Комментариев нет:
Отправить комментарий