#c #vector
#c #вектор
Вопрос:
Я хочу разработать класс, PrimitiveType
который служит абстрактным классом для математических объектов, таких как скаляр, вектор, тензор и т.д., Чтобы хранить их в std::vector<PrimitiveType *> myVector
, через который я могу выполнять итерации. Например, имея два из этих векторов одинакового размера, скажем, myVector1
и myVector2
, я хочу иметь возможность делать что-то вроде
for (size_t i = 0; i < myVector1.size(); i )
myVector1[i] = myVector2[i];
и не хочу заботиться о том, добавляю ли я скаляры, векторы или тензоры. До сих пор я придумывал
#include <algorithm>
#include <cstddef>
#include <iostream>
template<class T> class Scalar;
template<class T>
class PrimitiveType
{
protected:
size_t size_;
T *value_;
public:
virtual ~PrimitiveType() = 0;
PrimitiveType amp; operator =(const PrimitiveType amp;primitiveType)
{
for (size_t i = 0; i < size_; i )
value_[i] = primitiveType.value_[i];
return *this;
}
};
template<class T> PrimitiveType<T>::~PrimitiveType() {};
template<class T>
class Scalar : public PrimitiveType<T>
{
using PrimitiveType<T>::size_;
using PrimitiveType<T>::value_;
public:
Scalar(T value = 0.0)
{
size_ = 1;
value_ = new T(value);
}
~Scalar() { delete value_; }
operator T amp;() { return *value_; }
};
template<class T>
class Vector : public PrimitiveType<T>
{
using PrimitiveType<T>::size_;
using PrimitiveType<T>::value_;
public:
Vector(T value = 0.0)
{
size_ = 3;
value_ = new T[size_];
std::fill(value_, size_, value);
}
~Vector() { delete[] value_; }
T amp; operator()(size_t index) { return value_[index]; }
};
int main()
{
Scalar<double> s(3.2);
std::cout << s << std::endl;
static const size_t size = 3;
std::vector<PrimitiveType<double> *> p = std::vector<PrimitiveType<double> *>(size);
for (size_t i = 0; i < size; i )
{
p[i] = new Scalar<double>();
*(p[i]) = s;
std::cout << *static_cast<Scalar<double> *>(p[i]) << std::endl;
}
}
но я не думаю, что это очень чистое решение. В частности,
1) Я хотел бы иметь возможность использовать списки инициализаторов в дочерних классах, но возникают проблемы с поиском зависимого имени, например
ошибка: ‘using PrimitiveType::size_’ не является нестатическим элементом данных ‘Scalar’
Как реализовать что-то подобное Scalar(T value = 0.0) : size_(1) , value_(new T(value)) {}
?
2) На самом деле я бы предпочел создать value_
статический массив, потому что во время компиляции я знаю, какой размер value_
имеет значение для Scalar
, Vector
, … Конечно, это не выполняется для PrimitiveType
, однако экземпляр PrimitiveType
никогда не создается.
Комментарии:
1. Это выглядит как чрезвычайно сложное решение, и я не уверен, что понимаю его назначение. Не должно ли сложение быть совершенно другой операцией в зависимости от типа обоих операндов? Честно говоря, я не верю, что ООП здесь является хорошим выбором; я не могу представить полезную абстрактную операцию, которой все равно, имеет ли она дело с вектором или скаляром. В любом случае, смешивание ООП и перегрузки операторов (плюс шаблонизация!) часто является хорошим показателем того, что дизайн программы следует пересмотреть.
2. Первое правило наследования: не делай этого. Второе правило наследования (только для экспертов): пока этого не делайте.
3. На самом деле это не работает. Вы не можете, например, умножить две произвольные матрицы.
4. @ChristianHackl Вызов
operator =
конечно, имеет смысл только в том случае, если тип аргумента соответствует типу объекта, для которого вызывается метод. Не имеет никакого смысла вызыватьoperator =
тензор второго порядка и передавать тензор первого порядка (= вектор). Мало того, что это не имеет смысла, но функция также может привести к ошибке сегментации (или показать другое непреднамеренное поведение), поскольку осуществляется доступ к записи, которая не существует. Но я думаю, что может быть нормально возложить на вызывающего абонента обязанность передавать правильные аргументы.5. @Puppy Он предназначен не для умножения матриц произвольной формы, а только тензоров фиксированного размера, т.е. скаляров (1 компонент), векторов (3 компонента), тензоров второго порядка (9 компонентов, если они несимметричны) и так далее.
Ответ №1:
Редактировать: Завершите редактирование, потому что другое решение не подошло.
Что ж, самым простым способом решения вашей проблемы было бы переместить хранилище из основного класса в базовый класс и предоставить средство доступа к элементу:
template <class C>
class PrimitiveType {
public:
PrimitiveType amp; operator =(const PrimitiveType amp;primitiveType) {
if (this->_size() != primitiveType._size()) {
throw "Incompatible type." ;
}
for (size_t i = 0 ; i < this->_size() ; i) {
this->_get(i) = primitiveType._get(i) ;
}
return *this ;
}
protected:
virtual Camp; _get (size_t) = 0 ;
virtual C _get(size_t) const = 0 ;
virtual size_t _size () const = 0 ;
};
Затем в Scalar
и Vector
, например:
template <class C>
class Scalar : PrimitiveType <C> {
C _value ;
public:
Scalar (C constamp; c) : _value(c) { }
protected:
virtual Camp; _get (size_t) = 0 { return _value ; }
virtual C _get(size_t) const = 0 { return _value ; }
virtual size_t _size () const = 0 { return 1 ; }
};
template <class C, int N = 3>
class Vector : PrimitiveType <C> {
std::array <C, N> _values ;
public:
Scalar (std::initializer_list <C> l) : _values(l) { }
protected:
virtual Camp; _get (size_t i) = 0 { return _values(i) ; }
virtual C _get(size_t i) const = 0 { return _values(i) ; }
virtual size_t _size () const = 0 { return _values.size() ; }
};
Окончание редактирования.
Для вашего первого вопроса просто добавьте защищенный конструктор в PrimitiveType и вызовите его из вашего дочернего класса:
class PrimitiveType {
protected:
PrimitiveType (/* */) : _values(/* */), /* ... */ { }
};
class Scalar {
public:
Scalar (/* */) : PrimitiveType(/* */) { }
}
Для ваших вторых вопросов добавьте тип хранилища в качестве второго аргумента шаблонов примитивного типа:
template <class C, class S = std::vector <C>>
class PrimitiveType { /* */ }
template <class C>
class Scalar : public PrimitiveType <C, std::array <C, 1>> { /* */ }
Подробный пример:
template <class C, class S = std::vector <C>>
class PrimitiveType {
public:
PrimitiveType amp; operator =(const PrimitiveType amp;primitiveType) {
/** Same code as yours. **/
}
protected:
S _values ;
PrimitiveType (std::initializer_list <C> l) : _values(l) { }
};
template <class C>
class Scalar : public PrimitiveType <C, std::array <C, 1>> {
public:
Scalar (C constamp; c) : PrimitiveType({c}) { }
};
Комментарии:
1. Они не смогут иметь a
std::vector
с указателями на сочетаниеScalar
s,Vector
s иTensor
s, не так ли? Базовый класс для каждого из них будет разным.2. @JosephMansfield если вектор имеет тип PrimitiveType, он будет работать
3. @NathanWride Нет, потому что у вас есть несколько
PrimitiveType
экземпляров шаблонов с разными вариантами.4. @Holt Извините, теперь я понимаю
5. @JosephMansfield Я добавил большую правку в свой пост, теперь все должно быть в порядке, спасибо за ваш комментарий.