Перегрузка friend бинарного оператора шаблонного класса с определёнными типами C++
Необходимо переопределить перемножение матриц для разных структур, чтобы реализовать следующий метод
Matrix<Neuron> operator*(const Matrix<Weight>& arg_1, const Matrix<Neuron>& arg_2)
Проблема в следующем, данный метод приводит к duplicate symbols. Каким образом мне реализовать данный метод, чтобы тот был работоспособный?
Вот код
#ifndef MLP_SRC_MATRIX_H_
#define MLP_SRC_MATRIX_H_
#include <cstring>
#include <random>
#include <stdexcept>
struct Weight {
double data = {};
Weight() {
static std::random_device rd;
static std::default_random_engine generator(rd());
static std::uniform_real_distribution<double> distribution(-1.0, 1.0);
data = distribution(generator);
}
};
struct Neuron {
double data = {};
};
template <class T>
class Matrix {
public:
Matrix(){};
Matrix(const unsigned rows, const unsigned cols);
Matrix(const Matrix<T>& other);
~Matrix();
Matrix<T>& operator=(const Matrix<T>& other);
friend Matrix<Neuron> operator*(const Matrix<Weight>& arg_1, const Matrix<Neuron>& arg_2);
T& operator()(const unsigned row, const unsigned col);
const T& operator()(const unsigned row, const unsigned col) const;
const unsigned GetRows() const;
const unsigned GetCols() const;
private:
T** m_matrix = nullptr;
unsigned m_rows = 0U;
unsigned m_cols = 0U;
void Init(const unsigned rows, const unsigned cols);
void Destroy();
};
template <class T>
Matrix<T>::Matrix(const unsigned rows, const unsigned cols)
: m_rows{rows}, m_cols{cols} {
Init(m_rows, m_cols);
}
template <class T>
Matrix<T>::Matrix(const Matrix<T>& other) {
*this = other;
}
template <class T>
Matrix<T>::~Matrix() {
Destroy();
}
template <class T>
Matrix<T>& Matrix<T>::operator=(const Matrix<T>& other) {
if (this != &other) {
Destroy();
m_rows = other.m_rows;
m_cols = other.m_cols;
Init(m_rows, m_cols);
for (unsigned i = 0; i < m_rows; ++i) {
for (unsigned j = 0; j < m_cols; ++j) {
m_matrix[i][j] = other.m_matrix[i][j];
}
}
}
return *this;
}
template <class T>
T& Matrix<T>::operator()(const unsigned row, const unsigned col) {
return m_matrix[row][col];
}
template <class T>
const T& Matrix<T>::operator()(const unsigned row, const unsigned col) const {
return m_matrix[row][col];
}
template <class T>
const unsigned Matrix<T>::GetRows() const {
return m_rows;
}
template <class T>
const unsigned Matrix<T>::GetCols() const {
return m_cols;
}
template <class T>
void Matrix<T>::Init(const unsigned rows, const unsigned cols) {
m_matrix = new T*[rows];
for (unsigned i = 0; i < rows; ++i) {
m_matrix[i] = new T[cols]{};
}
}
template <class T>
inline void Matrix<T>::Destroy() {
if (m_matrix != nullptr) {
for (unsigned i = 0; i < m_rows; ++i) {
delete[] m_matrix[i];
}
delete[] m_matrix;
}
m_rows = m_cols = {};
}
Matrix<Neuron> operator*(const Matrix<Weight>& arg_1, const Matrix<Neuron>& arg_2) {
Matrix<Neuron> result(arg_1.GetRows(), arg_2.GetCols());
for (int i = 0; i < result.GetRows(); ++i) {
for (int j = 0; j < result.GetCols(); ++j) {
result(j, i).data = 0.f;
for (int k = 0; k < arg_1.GetCols(); ++k) {
result(i, j).data += arg_1(i, k).data * arg_2(k, j).data;
}
}
}
return result;
}
#endif // MLP_SRC_MATRIX_H_
Ответы (1 шт):
Попробовал ваш код - у меня ошибки нет.
Есть несколько замечаний по коду:
Какой смысл в двух одинаковых структурах struct Weight и struct Neuron. У них отличие, что Weight создается со случайным значением, а Neuron со значением по умолчанию? Проще в классе Matrix сделать функцию RandomInit() которая будет заполнять матрицу случайными значениями. И тогда вы будете работать только с одним типом Matrix<double>.
Зачем в функцию Init() передавать значения? Она является методом класса и переменные-члены класса ей доступны.
// rows и cols являются членами класса и доступны в функции
template <class T>
void Matrix<T>::Init(const unsigned rows, const unsigned cols)
{
m_matrix = new T*[rows];
for (unsigned i = 0; i < rows; ++i)
m_matrix[i] = new T[cols]{};
}
В деструкторе вы проверяете на nullptr только указатель на массив указателей. При этом, операция new() в общем случае может не выделить память. А вы не обнуляете создаваемые указатели в функции Init() и не проверяете на nullptr в деструкторе.
void Matrix<T>::Init(const unsigned rows, const unsigned cols)
{
m_matrix = new T*[rows];
for (unsigned i = 0; i < rows; ++i)
m_matrix[i] = new T[cols]{};
}
inline void Matrix<T>::Destroy()
{
if (m_matrix != nullptr) // здесь есть проверка
{
for (unsigned i = 0; i < m_rows; ++i)
delete[] m_matrix[i]; // а здесь нет проверки
delete[] m_matrix;
}
}
А самое главное - почему вы сами управляете памятью? Почему не используете контейнеры? Наступание повторно на те же грабли конечно полезно для запоминания материала, но зачем?
class Matrix
{
private:
vector< vector<T> > m_matrix;
В операторе умножения у вас ошибка - поменяны индексы местами. Вы обнуляете result(j, i).data = 0.f; а суммируете в другой элемент result(i, j).data +=. Ваше обнуление начнет портить уже посчитанный результат.
Операция умножения двух матриц выполнима только в том случае, если число столбцов в первом сомножителе равно числу строк во втором. Как минимум - нужно ввести такую проверку. У вас проверки нет, а циклы идут до количества строк и столбцов. Если они не совпадают вы можете выйти за пределы массива. Ну конечно возможно вы где-то делаете эту проверку, но логично её сделать именно в операторе умножения.
Matrix<Neuron> operator*(const Matrix<Weight>& arg_1, const Matrix<Neuron>& arg_2)
{
// вот здесь нужна проверка на совпадение !!!
Matrix<Neuron> result(arg_1.GetRows(), arg_2.GetCols());
for (int i = 0; i < result.GetRows(); ++i)
for (int j = 0; j < result.GetCols(); ++j)
{
result(j, i).data = 0.f; // испортит посчитанный результат
// result(i, j).data = 0.f; можно не делать, т.к. result инициирован значениями по-умолчанию
for (int k = 0; k < arg_1.GetCols(); ++k)
result(i, j).data += arg_1(i, k).data * arg_2(k, j).data;
}
return result;
}