Перегруженные операторы класса матрицы C ++, возвращаемые по ссылке

Я пишу шаблонный класс матрицы и получаю переполнение стека при возврате по значению из операторов: +, -, * для больших матриц. Я предпочел бы как-то вернуться по ссылке, чтобы освободить стек и избежать дополнительного копирования, но тогда мне придется возвращать объект, созданный с новый и нарушить общее правило «использования удалять для каждого новыйMsgstr «Я не могу вернуть по значению из-за проблем с копированием служебных данных и ограничения стека, и я также не могу вернуться по ссылке из-за утечек памяти, так что мне тогда делать?

Вот моя функция продукта (матрица содержит элементы 2D-массива):

    template<typename T, unsigned int n, unsigned int m> template<unsigned int m2>
Matrix<T,n,m2> Matrix<T,n,m>::operator*(Matrix<T,m,m2>& M) {
T prod[n][m2];
if(n*m < GPUAccelerationThreshold)
for(int i = 0; i < n; i++)
for(int j = 0; j < m2; j++) {
prod[i][j] = elems[i][0] * M(0, j);
for(int p = 1; p < m; p++)
prod[i][j] += elems[i][p] * M(p, j);
}
else {
array_view<T, 2> product(n, m2, *prod);
array_view<T, 2> a(n, m, *elems);
array_view<T, 2> b(m, m2, M.elems[0]);

parallel_for_each(
product.extent,
[=](index<2> idx) restrict(amp) {
int row = idx[0];
int col = idx[1];
for (int inner = 0; inner < m; inner++) {
product[idx] += a(row, inner) * b(inner, col);
}
}
);
product.synchronize();
}return Matrix<T,n,m2>(prod);
}

Я пишу этот класс, потому что я хочу улучшить некоторые матричные операции на GPU (с помощью MS amp). Я искал существующее решение, нашел библиотеки линейной алгебры с ускорением на GPU, но в них я не смог найти простой матричный класс с операторами +, -, *. Может быть, кто-нибудь мог бы порекомендовать меня?

2

Решение

Три быстрых комментария:

  • Традиционно, Matrix классы использовали динамический
    распределение. Вы не показываете свой Matrix класс, но если ваш
    данные:

    T myData [n] [м];
    

    Вы можете изменить это на:

    std :: vector myData;
    

    , инициализировать его в размер n * m в конструкторе и
    вычисление единого индекса в operator[] (который должен
    верните прокси, если хотите проверить границы).
    Кроме того, вы можете использовать operator()( int i, int j ) за
    доступ к элементу: ли myMatrix( i, j ) или же
    myMatrix[i][j] предпочтительнее для доступа зависит от того, кого вы спрашиваете.

    Хотя это решение немного увеличивает общее использование памяти (но
    очень немного), это уменьшает площадь стека до пары
    дюжина байтов, независимо от размера матрицы.

  • Также традиционно, у классов матрицы не было размеров как
    часть их аргументов шаблона. Будь это хорошо
    или не является спорным. Вы получаете намного лучшую проверку типов (и
    ошибки во время компиляции, а не во время выполнения) с вашим решением,
    но если размеры являются аргументами конструктора, скорее
    чем аргументы шаблона, вы можете прочитать их из командной строки
    или файл конфигурации или что-то еще. Это классическая безопасность
    Гибкость против гибкости.

    Что касается вашей проблемы, не имея размеры как
    Параметры шаблона означает, что все матрицы типа T иметь
    того же типа. Таким образом, вы можете получить доступ к внутренностям матрицы, которую вы
    вернуться из функции-члена, и вам больше не нужно
    промежуточный T prod[n][m2], Конечно, вы могли бы сделать все
    экземпляры Matrix друг или просто воспользуйтесь доступом
    функции для установки значений. Во всяком случае, вы делаете не хочу
    промежуточный T prod[n][m2]; это не только требует много на
    стек памяти, это означает, что вам придется копировать результаты.

  • Наконец, и это несколько сложнее: в лучшей матрице
    классы, operator* не возвращает матрицу, а помощник
    класс, в соответствии с:

    шаблон
    класс MatrixMultiply
    {
    L const * myLhs;
    R const * myRhs;
    общественности:
    typedef T value_type;
    MatrixMultiply (L const lhs, R const rhs)
    : myLhs ( лс)
    , myRhs ( rhs)
    {
    }
    int getX () const
    {
    return myLhs-> getX ();
    }
    int getY () const
    {
    return myRhs-> getY ();
    }
    T get (int i, int j) const
    {
    возвращаем расчитать IJ (myLhs, myRhs);
    }
    };

    Затем вы предоставляете шаблонный конструктор и оператор присваивания
    который использует getX(), getY() а также get( i, j ), Ваш
    operator* также шаблон, который возвращает
    MatrixMultiply:

    шаблон
    MatrixMultiply
    оператор * (L const lhs, R const rhs)
    {
    вернуть MatrixMultiply (lhs, rhs);
    }

    (Обратите внимание, что если L::value_type а также R::value_type не
    идентично, это не скомпилируется. Что ты хочешь, кроме
    что сообщения об ошибках будут далеко не ясны.)

    В результате вы никогда не строите промежуточное звено,
    временные матрицы.

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

    (Та же самая техника может использоваться с использованием абстрактного базового класса,
    сказать MatrixAccessor, с чистыми виртуальными геттерами и производными
    Matrix и все помощники любят MatrixMultiply от
    Это. ИМХО, это намного удобочитаемее и сообщения об ошибках
    из компилятора, безусловно, будет более понятным. Результаты будут
    так же, как компилятор фактически вставляет все члены
    функции. Но это большой если, так как может быть
    Значительная функция вложенности.)

3

Другие решения

Нет простого способа решить эту проблему. Вы не можете вернуть локальные переменные стека в качестве ссылки, потому что память «позади» переменной исчезнет, ​​когда вы вернетесь. Так что у вас должно быть где-то выделенное хранилище. Это не должно происходить из new / delete, но вам нужно иметь какое-то хранилище при копировании данных.

Одно из решений, конечно, будет иметь трехоперандную операцию, поэтому вместо:

a = b + c;

Вы используете функцию:

добавить (а, б, в);

где a, b и c являются ссылками.

Это делает код намного более запутанным, но я не могу придумать более очевидного способа решения проблемы — если вы не хотите писать свою собственную функцию выделения / удаления (или сборщика мусора).

1

На самом деле я не могу полностью понять вашу идею … Бинарные операторы принимают два аргумента и создают результат. Фактически вы можете видеть, что вы возвращаете новый созданный объект. Так что это единственный способ писать программы: выделять память, использовать ее, а затем удалять. На самом деле я даже не понимаю, что делает ваш конструктор. Если он просто копирует указатель на «prod», тогда матрица результата нарушается, когда вы возвращаете его из функции, потому что память «prod» будет удалена, как только функция вернется (потому что она создана в стеке). Так что вы не можете вернуть его по ссылке тоже.

То, как я вижу решение для выделения памяти в конструкторе матрицы. Если вы делаете его как шаблон в зависимости от размера матрицы, размер матрицы известен из параметров шаблона (я нахожу довольно странным создавать шаблоны с размером матрицы в качестве аргумента. В чем смысл?). Таким образом, вы выделяете память в конструкторе с помощью «new» и удаляете ее в деструкторе с помощью «delete». Таким образом, эта память будет распределена в соответствии с методологией RAII, которая довольно хорошо работает в ООП. Затем вы реализуете такие методы, как setElement (i, j, value), устанавливаете элементы в новой созданной матрице в вашем бинарном операторе и возвращаете его.

Однако есть некоторые проблемы, о которых я хочу позаботиться. Конструктор копирования должен действительно копировать матрицу, а не просто указатель (или несколько деструкторов будут пытаться уничтожить одну и ту же память), или вы можете запрограммировать модель «отложенного копирования», которая фактически копирует матрицу при изменении (см. Вики). Или вы можете сделать конструктор копирования закрытым без реализации (чтобы вообще не копировать).
Если вам не разрешено создавать такие методы, как «setElement», потому что вы не хотите, чтобы пользователь вашей библиотеки изменял значения матрицы, вы можете получить доступ к частным данным и методам (даже в объектах, которые мы получили в качестве аргумента или недавно создали ) в таких операторах, потому что вы находитесь внутри метода класса.

Если вам нужно передать необработанный указатель на другую функцию вычисления, как это делается в части «else», вы можете создать конструктор из указателя, который будет копировать только указатель (но это опасный способ. Если вы передаете указатель, вы не должны иметь к нему доступ. это ни к чему, потому что матричный класс теперь является боссом) или полностью копируйте данные (которые работают медленнее, но вы можете передавать туда указатели из стека или указатели, после которых вам нужно получить контроль) в зависимости от вашего желания, и деструктор будет очищать его при разрушении матрицы. Или вы даже можете создать закрытый метод, такой как «getRawMatrix ()», который будет возвращать необработанный указатель на данные из матрицы и передавать этот указатель в ваши функции вычисления или даже просто получать указатель необработанных данных, потому что вы находитесь внутри метода класса матрицы ,

Обычно вы выделяете память в конструкторе и создаете модель «ленивого копирования», потому что матрицы могут быть огромными. Внутри класса разрешен доступ к личным данным и членам, то есть классы сделаны. Я надеюсь, что это будет полезно ..

1