Короткая квадратная матрица класса в C++, используя массив


Я новичок в C++ в сравнении с другими языками, так как тренировки я написал Матрица квадратная класса, который не использует STL.

#ifndef MATRIX_H
#define MATRIX_H
#include <initializer_list>
#include <algorithm>


class Matrix {
public:
  Matrix(std::size_t sz) : elements{new int[sz * sz]}, size{sz} {}
  Matrix(std::size_t sz, std::initializer_list<int> elem) : Matrix(sz) {
    std::copy(elem.begin(), elem.end(), elements);
  }
  ~Matrix() { delete[] elements; }

  /** Get the matrix element at row i and column j. */
  int get(std::size_t i, std::size_t j) const {
    return elements[convert(i, j)];
  }

  /** Set the value of the matrix at row i and column j and return the old value. */
  int set(std::size_t i, std::size_t j, int new_value) {
    std::size_t idx = convert(i, j);
    int old_value = elements[idx];
    elements[idx] = new_value;
    return old_value;
  }

  std::size_t width() const {
    return size;
  }


private:
  int *elements;
  std::size_t size;

  inline std::size_t convert(std::size_t i, std::size_t j) const {
    if (i >= size || j >= size) {
      throw std::exception("Matrix indices out of bounds");
    }
    std::size_t idx = i * size + j;
    return idx;
  }
};

#endif

Критика приветствуются! В частности, я был бы рад получить советы по следующим вопросам:

  1. Это уместное использование обработки исключений? Есть лучший вариант, чем просто подбрасывание вверх std::exception если матрица индексов находятся вне границ?
  2. Сделать конструкторы и деструкторы смысл?
  3. Любые общие замечания писать в идиоматических стиле C++.


563
8
задан 30 января 2018 в 08:01 Источник Поделиться
Комментарии
2 ответа

(1) исключением является хорошим способом обработки подобных ошибок, но это может быть хорошей идеей, чтобы обеспечить не бросая, менее безопасных методов доступа. Например, std::vector::at() бросит std::out_of_range если запрошенный индекс является недействительной, но std::vector::operator[] будет молча принимать все значения. Это потому, что, если отдельные элементы используются очень часто (какой-то непонятный алгоритм математика), все эти IFS может повлиять на производительность. Так, как она идет за исключением, два предложения:


  • бросить std::out_of_range вместо обычного std::exception чтобы указать, какую ошибку только что произошло,

  • не бросать в каждый метод доступа (вы только один, что приводит к точке 4, см. ниже).

(2) Да, они это делают. Но, основной вопрос: вы действительно должны быть в состоянии указать размер матрицы во время выполнения? Возможно, было бы лучше использовать константу времени компиляции размер:

template <size_t Size>
class Matrix
{
private:
int elements[Size*Size];
//...
};

Затем, вы могли бы использовать это так:

Matrix<4> mat4;

В этой версии, без деструктор будет необходимо и без выделения памяти. Из-за этого, эта версия также может быть быстрее.

(3) "идиоматические c++ стиля" стала очень хрупкий термин в последнее время (ИМХО). Но, вы, вероятно, может оказать поддержку для доступа к элементам матрицы с помощью operator[] (см. пункт 4).

(4) было бы неплохо, если бы пользователи вашей матрицы могли бы написать просто:

Matrix<4> mat; // Or: Matrix mat {4}, in dynamically-allocated version
mat[3][3] = 1; // set elements in row 3, column 3 (note, that indices are from 0 to 3)

Или, что эквивалентно, в простом варианте:

mat[15] = 1;

(*) Простой версии только требуется наличие operator[]:

template <size_t Size>
class Matrix
{
int& operator[](size_t index)
{
return elements[index];
}

const int& operator[](size_t index) const
{
return elements[index];
}
};

Обратите внимание, что имеются две версии, так что вы можете сделать это:

Matrix<4> mat;
mat[5] = 1;

но не этого:

const Matrix<4> constMat;
constMat[2] = 3; // compilation error: cannot assign to const reference

(**) Более изящным, но и более сложная версия (вложенные [][] синтаксис), требует, что operator[] матрицы должен возвращать объект (или массив), который также поддерживает [] нотации.

Более простой способ добиться этого было бы изменить T* elements для T** elements (или, в шаблонной версии, elements[Size*Size] для elements[Size][Size]). Обратите внимание, что в этом случае вам не нужны convert() функция, поскольку вы можете использовать индексы явно.

Вы могли бы также объявить простой Vector структура (я ограничусь здесь в шаблонный вариант):

template <size_t Size>
struct Vector
{
private:
int elements[Size];

public:
int& operator[](size_t index)
{
return elements[index];
}

const int& operator[](size_t index) const
{
return elements[index];
}
};

И затем реализовать Matrix используя Vector:

template <size_t Size>
class Matrix
{
private:
Vector<Size> columns[Size];

public:
Vector<Size>& operator[](size_t index)
{
return columns[index];
}

const Vector<Size>& operator[](size_t index) const
{
return columns[index];
}
};

(1.1) теперь, после того, как мы освещали тему operator[]мы можем вернуться к исключениям еще раз. К помощи этих небезопасных operator[]s, мы можем добавить более безопасным, явные at() функции:

template <size_t Size>
class Matrix
{
private:
Vector<Size> columns[Size];

public:
Vector<Size>& at(size_t index)
{
if (index >= Size) throw std::out_of_range("Invalid index!");
return columns[index];
}

const Vector<Size>& at(size_t index) const
{
if (index >= Size) throw std::out_of_range("Invalid index!");
return columns[index];
}
};

7
ответ дан 30 января 2018 в 10:01 Источник Поделиться

Вопросы


Это уместное использование обработки исключений?

Нет. Вы должны иметь возможность иметь доступ к проверено. Но в большинстве случаев вы уже проверили ряд. Таким образом, принудительная проверка на каждом доступы становится излишним.

Стандартный вариант использования может выглядеть следующим образом:

for(int loopX = 0; loopX < m.width(); ++loopX) {
for(int loopY = 0; loopY < m.width(); ++loopY) {
std::cout << m.get(loopX, loopY) << " ";
}
}

В этой ситуации вы знаете, что оба loopX и loopY гарантируется, чтобы быть в правильном диапазоне. Так почему же там не-нужен тест, чтобы увидеть, если они находятся в правильном диапазоне.

Но даже std::vector есть проверенная версия пользователь. См vector::at(). Хотя стандартные доступы член не проверял.


Есть лучший вариант, чем просто подбрасывая вверх std::исключение, если матрица индексов выходит за рамки дозволенного?

Когда вы проверить четыре из диапазона ситуациях вы могли бы использовать std::out_of_range


Сделать конструкторы и деструкторы смысл?

Конечно. Насколько они идут.
Но вы сделали большой ошибкой начинающих не внедряя правило трех. В результате вы открыли для себя много потенциальных проблем с удалением матрицы.

Matrix   x(5);     // Your constructor.
Matrix y(x); // Compiler generated constructor.
// This will compile. But because your class contains
// a RAW owned pointer (you delete the pointer) your
// class is not going to work as expected when it goes
// out of scope.

Найдите Правило 3 и реализовать это.


Любые общие замечания писать в идиоматических стиле C++.


  1. Правило трех

  2. Не делайте вещи дорогие для опытных пользователей, просто подержать за руку новичка.

  3. Реализовать семантику перемещения (перемещения дешевле, чем копирование).

  4. Использовать [] оператор, а не получить/установить (хотя вы можете иметь оба).

  5. Подумайте о том, как ваш класс будет использоваться со стандартной библиотекой.
    Вы можете добавить итераторы к нему (что дает вам доступ к алгоритмам).

  6. Вы хоть про стандартные матричные операции и как они могут быть оптимизированы.

Дизайн

Доступ к элементам матрицы обычно делается с [] в Математика. Чтобы сделать вашу матрицу легко использовать и другие математика, как человек может быть, вы должны реализовать это (или что-то близкое).

Простой В1 технику:

 int&  operator()(int x, int y);   // Not [] but a close relative
// It allows a simple way to use 2
// dimensions with little work.

// Don't forget the const version
int const& operator()(int x, int y) const;

Кроме того сложнее фактический [] оператора. Проблема здесь заключается в том, что [] могу взять только одного индекса. Значит вам нужен промежуточный объект, чтобы сделать это правильно. @Матеуш Grzejek сделали хорошую первую версию. Но ее немного неэффективным, так как требует копию линии.

 // A quick way to do it simply.
// You will need to add the const versions of stuff yourself.
class Matrix
{
private:
int& access(int x, int y);
int& operator()(int x, int y) {return access(x, y);}

// Row is cheap to create and copy.
// Allows you to get a reference to a row and then
// access an element in the Row.
class Row {
Matrix* parent;
int x;
public:
Row(Matrix* p, int x)
: parent(p)
, x(x)
{}
int& operator[](int y) {return parent->access(x, y);}
};
Row operator[](int x){return Row(this, x);}
};

Комментарий Код

Ничего особенного, чтобы сказать.

2
ответ дан 30 января 2018 в 07:01 Источник Поделиться