jeudi 10 septembre 2020

How to do Operator overloading with move semantics in c++? (Elegantly)

class T {
    size_t *pData;          // Memory allocated in the constructor
    friend T operator+(const T& a, const T& b);
};
T operator+(const T& a, const T& b){        // Op 1
        T c;                            // malloc()
        *c.pData = *a.pData + *b.pData;
        return c;
}

T do_something(){
    /* Implementation details */
    return T_Obj;
}

A simple class T with dynamic memory. Consider

T a,b,c;
c = a + b;                                      // Case 1
c = a + do_something(b);            // Case 2
c = do_something(a) + b;            // Case 3
c = do_something(a) + do_something(b);           // Case 4
  • Case 1 uses 1 malloc()
  • Case 2 uses 2 malloc()
  • Case 3 uses 2 malloc()
  • Case 4 uses 3 malloc()

We can do better by addiitonally defining,

T& operator+(const T& a, const T&& b){           // Op 2
                    // no malloc() steeling data from b rvalue
        *b.pData = *a.pData + *b.pData;
        return b;
}

Case 2 now only uses 1 malloc(), but what about Case 3? do we need to define Op 3?

T& operator+(const T&& a, const T& b){            // Op 3
                    // no malloc() steeling data from a rvalue
        *b.pData = *a.pData + *b.pData;
        return b;
}

Further, if we do define Op 2 and Op 3, given the fact that an rvalue reference can bind to an lvalue reference, the compiler now has two equally plausible function definitions to call in Case 4

T& operator+(const T& a, const T&& b);        // Op 2 rvalue binding to a
T& operator+(const T&& a, const T& b);        // Op 3 rvalue binding to b

the compiler would complain about an ambiguous function call, would defining Op 4 help work around the compiler's ambiguous function call problem? as we gain no additional performance with Op 4

T& operator+(const T&& a, const T&& b){          // Op 4
                    // no malloc() can steel data from a or b rvalue
        *b.pData = *a.pData + *b.pData;
        return b;
}

With Op 1, Op 2, Op 3 and Op 4, we have

  • Case 1: 1 malloc (Op 1 is called)
  • Case 2: 1 malloc (Op 2 is called)
  • Case 3: 1 malloc (Op 3 is called)
  • Case 4: 1 malloc (Op 4 is called)

If all my understanding is correct, we will need four function signatures per operator. This somehow doesn't seem right, as it is quite a lot of boilerplate and code duplication per operator. Am I missing something? Is there an elegant way of achieving the same?

Aucun commentaire:

Enregistrer un commentaire