Переопределение операторов присваивания в прокси-классе для векторного объекта в матрице? - PullRequest
0 голосов
/ 14 ноября 2018

У меня небольшая проблема. У меня есть класс Matrix, определенный следующим образом (в мажорной строке):

template<typename T>
class Matrix {
    private:
        class RowVector {
            private:
                T *_vec;
                std::size_t _l; 

            public:
                RowVector(T *vec, std::size_t l); 

                const T &operator[](std::size_t index) const;
                T &operator[](std::size_t index);

                operator std::vector<T>() const;
        };

        std::vector<T> _data;
        std::size_t _m; 
        std::size_t _n; 

    public:
        Matrix(std::size_t m, size_t n, const T &elem = T());

        const RowVector operator[](std::size_t index) const;
        RowVector operator[](std::size_t index);

        std::size_t getm() const;
        std::size_t getn() const;
        void fill(const T &elem);
        void fillRow(std::size_t index, const T &elem);
        void fillCol(std::size_t index, const T &elem);

        Matrix &transpose(unsigned int i = 1);

        const std::vector<T> &data() const;
};

и хотите перегрузить два оператора RowVector =

typename Matrix<T>::RowVector &operator=(const std::vector<T> &vec);
typename Matrix<T>::RowVector &operator=(const Matrix<T> &mat);

, поэтому я могу вернуть RowVector &, используя A [0], и переназначить его значение, используя вектор или матрицу. Имейте в виду, что я (предположительно) могу игнорировать правило трех, потому что я не предоставляю клиенту явного способа создания RowVector объекта.

Однако, пытаясь написать тела функций для перегрузок, я столкнулся с проблемой:

(1) Я не могу скопировать-построить объект Vector / Matrix, который будет сохраняться вне области действия оператора =, так что я могу назначить его data() на _vec и его size() на _l.

(2) Я не могу напрямую изменить _data, так как это не статическая переменная; даже если бы я мог, у меня нет способа обнаружить индекс, чтобы я мог перезаписать соответствующую область памяти в окружающем объекте Matrix.

Знаете ли вы, каким образом это можно сделать? Это были бы два очень полезных актива для моего класса.

Я бы хотел написать что-то вроде этого:

Matrix<int> A(3, 4);
std::vector<int> v {1, 2, 3, 4};
Matrix<int> row(1, 4, 3);
// *****************
A[0] = v;
A[1] = row;
// *****************

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

Спасибо!

1 Ответ

0 голосов
/ 14 ноября 2018
T *_vec;
std::size_t _l;

Это проблемный дизайн! Я не говорю, что это неправильно само по себе, но тогда вам нужно правильно управлять памятью самостоятельно. Игнорирование правила трех (пяти) очень опасно в этом отношении. У вас есть указатель на (потенциально?) Динамически распределенную память, поэтому должен быть некоторый экземпляр, который отвечает за его удаление (не обязательно должен быть ваш RowVector, но что еще тогда?).

С чисто технической точки зрения, вы даже можете позволить _vec указывать на данные какого-либо вектора, при условии вы гарантируете, что этот другой вектор будет существовать столько, сколько вы хотите получить доступ к данным через указатель & Ndash; что в целом требует, однако, довольно определенных усилий.

Самым безопасным было бы, чтобы каждая строка поддерживала свои собственные данные, копируя (или перемещая) их из другого вектора. Тогда проще всего сохранить данные в отдельном std::vector (заменив необработанный указатель).

Если вы хотите избежать копирования данных и вместо этого обмениваться данными между различными матрицами и их строками, то вы можете поддерживать данные через std::shared_ptr - либо поддерживая необработанный массив, либо, возможно, даже кучу -распределенный std::vector.

Если вы выберете std::vector или std::shared_ptr, тогда конструкторы копирования и перемещения и операторы присваивания станут совершенно простыми:

class C
{
public:
    C(C const&) = default;
    C(C&&) = default;
    C& operator= (C const&) = default;
    C& operator= (C&&) = default;
};

Все эти значения по умолчанию будут выполнять копирование / перемещение в соответствии с элементом, и в std::vector и std::shared_ptr уже есть соответствующие конструкторы и операторы, так что вы будете в порядке & ndash; и теперь вы можете нарушить правило пяти, отбросив деструктор, так как достаточно стандартного (вызывающего все деструкторы члена).

Если вы рассматриваете общие указатели: имейте в виду, что затем вы не можете назначить данные std::vector для: std::vector выполняет свое собственное управление памятью, и в результате вы получите двойное удаление, поэтому в данном конкретном случае вам все равно придется создать копию. Вы можете оказаться в нескольких конструкторах и операторах присваивания:

std::shared_ptr<std::vector<int>> _data;

// assign shared pointers
RowVector(RowVector const&) = default;
RowVector(RowVector&&) = default;

// need to create copies of: we never know about the scope of the vector passed!
RowVector(std::vector<int> const& data) : _data(new std::vector<int>(data)) { }
RowVector(std::vector<int>&& data) : _data(new std::vector<int>(std::move(data))) { }

// we *are* sharing already -> no need to copy:
RowVector(std::shared_ptr<std::vector<int>& data) : _data(data) { }

Операторы присваивания аналогично.

Примечание: если вы хотите иметь математическую матрицу nxm, вы наверняка не хотите иметь зубчатый массив. Я бы предположил, что ваш Matrix class 'конструктор уже создает соответствующий вектор векторов, тогда для присваивания вам еще нужно проверить длину:

// defaults not suitable any more!
RowVector& RowVector::operator=(RowVector const& other)
{
    // still assuming shared pointer:
    // (for vector, replace -> with .)
    if(other._data->size() != _data.size())
    {
        throw SomeException();
    }
    _data = other._data;
}
RowVector(RowVector&& other)
{
    if(other._data->size() != _data.size())
    {
        throw SomeException();
    }
    _data = std::move(other._data);
}
...