Я полагаю, что мне следует проиллюстрировать мой комментарий о параметризации размеров матрицы, поскольку вы, возможно, не видели эту технику раньше.до 1.0, как:
Matrix<float, 2, 4> testArray(1.0);
Обратите внимание, что не требуется, чтобы хранилище находилось в куче (то есть, используя operator new
), так как размер фиксирован.Вы можете разместить это в стеке.
Вы можете создать еще пару матриц int
s:
Matrix<int, 2, 4> testArrayIntA(2);
Matrix<int, 4, 2> testArrayIntB(100);
Для копирования размеры должны совпадать, хотя типы не совпадают:
Matrix<float, 2, 4> testArray2(testArrayIntA); // works
Matrix<float, 2, 4> testArray3(testArrayIntB); // compile error
// No implementation for mismatched dimensions.
testArray = testArrayIntA; // works
testArray = testArrayIntB; // compile error, same reason
Умножение должно иметь правильные размеры:
Matrix<float, 2, 2> testArrayMult(testArray * testArrayIntB); // works
Matrix<float, 4, 4> testArrayMult2(testArray * testArrayIntB); // compile error
Matrix<float, 4, 4> testArrayMult2(testArrayIntB * testArray); // works
Обратите внимание, что при наличии ошибки она перехватывается во время компиляции. Это возможно, только если размеры матрицыисправлены во время компиляции, хотя.Также обратите внимание, что эта проверка границ приводит к без дополнительного кода времени выполнения. Это тот же код, который вы получили бы, если бы вы только установили размеры постоянными.
Изменение размера
Если вы не знаете размеры вашей матрицы во время компиляции, но должны подождать до времени выполнения, этот код может оказаться бесполезным.Вам нужно написать класс, который будет внутренне хранить измерения и указатель на фактические данные, и он должен будет делать все во время выполнения.Подсказка: напишите свой operator []
, чтобы рассматривать матрицу как измененный вектор 1xN или Nx1, и используйте operator ()
для выполнения многоиндексных обращений.Это связано с тем, что operator []
может принимать только один параметр, но operator ()
такого ограничения не имеет.Легко выстрелить себе в ногу (по крайней мере, заставить оптимизатор сдаться), пытаясь поддерживать синтаксис M[x][y]
.
При этом, если есть какое-то стандартное изменение размера матрицы, которое вы делаете дляизмените размер одного Matrix
на другой , учитывая, что все измерения известны во время компиляции , тогда вы можете написать функцию для изменения размера.Например, эта шаблонная функция преобразует любой Matrix
в вектор-столбец:
template<class T, size_t NRows, size_t NCols>
Matrix<T, NRows * NCols, 1> column_vector(const Matrix<T, NRows, NCols>& original)
{ Matrix<T, NRows * NCols, 1> result;
for(size_t i = 0; i < NRows; ++i)
for(size_t j = 0; j < NCols; ++j)
result.data[i * NCols + j][0] = original.data[i][j];
// Or use the following if you want to be sure things are really optimized.
/*for(size_t i = 0; i < NRows * NCols; ++i)
static_cast<T*>(result.data)[i] = static_cast<T*>(original.data)[i];
*/
// (It could be reinterpret_cast instead of static_cast. I haven't tested
// this. Note that the optimizer may be smart enough to generate the same
// code for both versions. Test yours to be sure; if they generate the
// same code, prefer the more legible earlier version.)
return result;
}
... ну, я думаю, что это вектор-столбец, в любом случае.Надеюсь, очевидно, как это исправить, если нет.В любом случае оптимизатор увидит, что вы возвращаете result
, и удалит лишние операции копирования, в основном построив результат именно там, где его хочет видеть вызывающий объект.
Проверка корректности измерения во время компиляции
Допустим, мы хотим, чтобы компилятор остановился, если размерность равна 0
(обычно это приводит к пустому Matrix
).Я слышал трюк под названием «Утверждение времени компиляции», который использует специализацию шаблонов и объявляется как:
template<bool Test> struct compiler_assert;
template<> struct compiler_assert<true> {};
Это позволяет вам писать код, такой как:
private:
static const compiler_assert<(NRows > 0)> test_row_count;
static const compiler_assert<(NCols > 0)> test_col_count;
Основная идея состоит в том, что, если условие true
, шаблон превращается в пустой struct
, который никто не использует, и молча отбрасывается.Но если это false
, компилятор не может найти определение для struct compiler_assert<false>
(просто объявление , которого недостаточно) и выдает ошибки.
Лучше версия Андрея Александреску (из его книги ), которая позволяет использовать объявленное имя объекта утверждения в качестве сообщения об импровизированной ошибке:
template<bool> struct CompileTimeChecker
{ CompileTimeChecker(...); };
template<> struct CompileTimeChecker<false> {};
#define STATIC_CHECK(expr, msg) { class ERROR_##msg {}; \
(void)sizeof(CompileTimeChecker<(expr)>(ERROR_##msg())); }
Для чего вы заполняетеmsg
должен быть действительным идентификатором (только буквы, цифры и символы подчеркивания), но это не так уж важно.Затем мы просто заменим конструктор по умолчанию:
Matrix()
{ // `data` gets its default constructor, which for simple types
// like `float` means uninitialized, just like C.
STATIC_CHECK(NRows > 0, NRows_Is_Zero);
STATIC_CHECK(NCols > 0, NCols_Is_Zero);
}
И вуаля, компилятор остановится, если мы по ошибке установим одно из измерений на 0
.О том, как это работает, см. Стр. 25 Книга Андрея .Обратите внимание, что в случае true
сгенерированный код отбрасывается до тех пор, пока у теста нет побочных эффектов, поэтому нет раздувания.