Home > Blockchain >  Is there a better way to overload operators on objects without making tons of copies?
Is there a better way to overload operators on objects without making tons of copies?

Time:08-02

So I wrote a very simplified example of what I'm trying to accomplish which in this case is a simple "matrix" struct. As expected this program will compile and run (gcc 9.4 on ubuntu 20.04) and provide a valid result for the matrix D, however the problem is that I can't use references as arguments for the functions and overloaded operators (I've tried and it just spits out undefined references) and which will result in a ton of copies of the object "matrix" to evaluate matrix<int> D = C^(ExpFunc(A B)).

So the question is: What would be the "correct" or better way to accomplish this? Thank you!!

(before you go all out recommending all those great libs that will handle this way way better and will be more reliable and etc.. I want to do this from scratch to learn and improve my skills and knowledge)

//f.h file
#include <iostream>
#include <vector>
#include <math.h>

template <typename T> struct matrix
{
    std::vector<T> data;
    size_t lines, columns;

    matrix() = default;
    matrix(const size_t L, const size_t C);
    void print();
    
};

//operation A   B = result
template <typename T> matrix<T> operator (matrix<T> A,  matrix<T> B);
//Hadamard product of A # B
template <typename T> matrix<T> operator^(matrix<T> A,  matrix<T> B);
//element-wise e^x function
template <typename T> matrix<T> ExpFunc(matrix<T> A);
//f.cpp file
#include "f.h"
template <typename T> matrix<T>::matrix(const size_t L, const size_t C){
    matrix<T>::data.resize(L * C);
    matrix<T>::lines = L;
    matrix<T>::columns = C;
}
template matrix<int>::matrix(const size_t L, const size_t C);

template <typename T> void matrix<T>::print(){
    for (size_t i = 1; i < matrix<T>::lines   1; i  ) {
        std::cout << "| ";
        for (size_t j = 1; j < matrix<T>::columns   1; j  ) {
            std::cout << matrix<T>::data[(i - 1) * matrix<T>::columns   (j - 1)] << " ";
        }
        std::cout << "|" << std::endl;
    }
    std::cout << "--------------" << std::endl;
}
template void matrix<int>::print();

template <typename T> matrix<T> ExpFunc(matrix<T> A){
    for (auto& ie : A.data)
        ie = exp(ie);
    return A;
}
template matrix<int> ExpFunc(matrix<int> A);

//operation A   B = result
template <typename T> matrix<T> operator (matrix<T> A,  matrix<T> B){
    for (size_t i = 0; i < A.data.size(); i  ){
        A.data[i]  = B.data[i];
    }
    return A;
}
template matrix<int> operator ( matrix<int> A,  matrix<int> B);

template <typename T> matrix<T> operator^( matrix<T> A,  matrix<T> B){
    for (size_t i = 0; i < A.data.size(); i  ){
        A.data[i] *= B.data[i];
    }
    return A;
}
template matrix<int> operator^( matrix<int> A,  matrix<int> B);

And the main.cpp file

#include "f.h"

int main(){   
    matrix<int> A(3,3), B(3,3), C(3,3);
    A.data = std::vector<int>{0,0,0,1,1,1,2,2,2};
    B.data = std::vector<int>{9,1,8,2,7,3,6,5,4};
    C.data = std::vector<int>{1,2,3,4,5,6,7,8,9};
    matrix<int> D = C^(ExpFunc(A   B));
    D.print();
    return 0;
}

CodePudding user response:

This compiles and should work fine:

#include <vector>


template <typename T> struct matrix
{
    std::vector<T> data;
    std::size_t lines, columns;

    matrix() = default;
    matrix(const std::size_t L, const std::size_t C);
    void print();
};

template <typename T>
matrix<T> operator (const matrix<T>& A, const matrix<T>& B);

template <typename T>
matrix<T> operator (const matrix<T>& A, const matrix<T>& B)
{
    matrix<T> rtrn(A.lines, A.columns);
    for (std::size_t i = 0; i < A.data.size(); i  )
        rtrn.data[i] = A.data[i]   B.data[i];
    return rtrn;
}

You could also keep the current pattern of using the left input as a copy for the output. It is reasonably efficient but may increase code size.

template <typename T>
matrix<T> operator (matrix<T> A, const matrix<T>& B);

template <typename T>
matrix<T> operator (matrix<T> A, const matrix<T>& B)
{
    for (std::size_t i = 0; i < A.data.size(); i  )
        A.data[i]  = B.data[i];
    return A;
}

Normally, I would advise against this pattern because it invokes a copy that could instead be folded into the computation (reducing the total number of memory operations). However, you use std::vector internally and that always zero-initializes its elements. Therefore compared to the version above, the copy only replaces one memset with a memcpy. Not much worse.

Further reduction in allocations

If you want to reduce the number of temporary vectors further in chained operations such as C^(ExpFunc(A B)) you can change the operations to work on expression objects, similar to what Eigen does.

  • Related