Абстрактный базовый класс для скаляра, вектора, тензора,

#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 Я добавил большую правку в свой пост, теперь все должно быть в порядке, спасибо за ваш комментарий.