Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c++ operator overloading memory question

In c++ you can create new instances of a class on both the heap and stack. When overloading an operator are you able to instantiate on the stack in a way that makes sense?

As I understood it an instance that sits on the stack is removed as soon as the function is done executing. This makes it seems as though returning a new instance sitting on the stack would be a problem.

I am writing this knowing there has to be a way, but I am not sure what the best practice is. If I have some class that is designed to always reside in the stack how do I go about operator overloading?

Any info would be helpful, thanks

{EDIT} I am overloading the + operator. Right now I use this code

Point Point::operator+ (Point a)
{
Point *c = new Point(this->x+a.x,this->y+ a.y);
return *c;
}

I was skeptical about instantiating c like so:

Point c(this->x + a.x, this->y, a.y);

because that would allocate it to the stack. My concern is that the stack pointer is going to change once this function finishes executing, and the instance will no longer be safe since any new local variables defined could erase it. Is this not a concern?

like image 547
Ori Avatar asked Dec 03 '22 14:12

Ori


2 Answers

If you're talking about for example operator+, where the object returned is not either of those input, then the answer is you instantiate on the stack and return by value:

struct SomeClass {
    int value;
};

SomeClass operator+(const SomeClass &lhs, const SomeClass &rhs) {
    SomeClass retval;
    retval.value = lhs.value + rhs.value;
    return retval;
}

or

class SomeClass {
    int value;
public:
    SomeClass operator+(const SomeClass &rhs) const {
        SomeClass retval;
        retval.value = this->value + rhs.value;
        return retval;
    }
};

or even:

class SomeClass {
    int value;
public:
    SomeClass(int v) : value(v) {}
    friend SomeClass operator+(const SomeClass &lhs, const SomeClass &rhs) {
        return SomeClass(lhs.value + rhs.value);
    }
};

The compiler then worries about where (on the stack) the return value is actually stored.

It will for example apply return-value optimizations if it can, but in principle what's happening is "as-if" the work you do constructs some value on the stack of your operator overload, and then at return this is copied to wherever it needs to be next. If the caller assigns the return value, it's copied there. If the caller passes it by value to some other function, it's copied wherever the calling convention says it needs to be in order to be that function parameter. If the caller takes a const reference, then it's copied to a temporary hidden away on the stack.

like image 171
Steve Jessop Avatar answered Dec 09 '22 15:12

Steve Jessop


C++ : RAII and Temporaries

You're right about objects on stack being destroyed once going out of scope.

But you ignore that C++ will use temporary objects are necessary. You must learn when a temporary variable will be created (and then optimized away) by the compiler for your code to work.

Temporary Objects

Note that in the following, I'm describing a very simplified "pure" viewpoint of what's happening: Compilers can and will do optimizations, and among them, will remove useless temporaries... But the behavior remains the same.

Integers?

Let's start slowly: What is supposed to happen when you play with integers:

int a, b, c, d ;
// etc.
a = b + (c * d) ;

The code above could be written as:

int a, b, c, d ;
// etc.
int cd = c * d ;
int bcd = b + cd ;
a = bcd ;

Parameters by value

When you call a function with a parameter passed "by value", the compiler will make a temporary copy of it (calling the copy constructor). And if you return from a function "by value", the compiler will, again, make a temporary copy of it.

Let's imagine an object of type T. The following code:

T foo(T t)
{
   t *= 2 ;

   return t ;
}

void bar()
{
   T t0, t1 ;

   // etc.

   t1 = foor(t0) ;
}

could be written as the following inlined code:

void bar()
{
   T t0, t1 ;

   // etc.

   T tempA(t1)     // INSIDE FOO : foo(t0) ;
   tempA += 2 ;    // INSIDE FOO : t *= 2 ;
   T tempB(tempA)  // INSIDE FOO : return t ;

   t1 = tempB ;    // t1 = foo...
}

So, despite the fact you don't write code, calling or returning from a function will (potentially) add a lot of "invisible code", needed to pass data from one level of the stack to the next/previous.

Again, you need to remember that the C++ compiler will optimize away most temporary, so what could be seen as an innefficient process is just an idea, nothing else.

About your code

Your code will leak: You "new" an object, and don't delete it.

Despite your misgivings, the right code should be more like:

Point Point::operator+ (Point a)
{
   Point c = Point(this->x+a.x,this->y+ a.y) ;
   return c ;
}

Which with the following code:

void bar()
{
    Point x, y, z ;
    // etc.
    x = y + z ;
}

Will produce the following pseudo code:

void bar()
{
    Point x, y, z ;
    // etc.
    Point tempA = z ;  // INSIDE operator + : Point::operator+ (Point a)
    Point c = z ;      // INSIDE operator + : Point c = Point(this->x+a.x,this->y+ a.y) ;
    Point tempB = c ;  // INSIDE operator + : return c ;

    x = tempB ;        // x = y + z ;
}

About your code, version 2

You make too much temporaries. Of course, the compiler will probably remove them, but then, no need to take sloppy habits.

You should at the very least write the code as:

inline Point Point::operator+ (const Point & a)
{
   return Point(this->x+a.x,this->y+ a.y) ;
}
like image 22
paercebal Avatar answered Dec 09 '22 14:12

paercebal