C ++ operator overloads memory issue

In C ++, you can create new instances of a class for both the heap and the stack. When you overload an operator, you can instantiate it on the stack, what makes sense?

As I understand it, the instance that sits on the stack is removed as soon as the function is executed. This gives the impression that returning a new instance sitting on the stack would be a problem.

I write this knowledge that there should be a way, but I'm not sure what the best practice is. If I have a class that must always be on the stack, how do I overload the operator?

Any information would be helpful, thanks

{} EDIT I ​​am overloading the + operator. Right now I am using 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 it will be allocated on the stack. I'm worried that the stack pointer will change after this function completes and the instance will no longer be safe as any new local variables might delete it. It's not a problem?

+2


source to share


6 answers


If you say, for example, operator+

where the returned object is none of these inputs, then you create the response 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.

This will, for example, apply return value optimizations if possible, but basically what happens is "as if" the work you do creates some value on the stack of your operator overload, and then on return is copied to wherever it is was not. If the caller assigns a return value, it gets copied there. If the caller passes it by value to some other function, it gets copied wherever the calling convention says it must be in order to be that function parameter. If the caller accepts a constant reference, it is copied to a temporary folder hidden on the stack.

+12


source


C ++: RAII and Temporaries

You are correct that objects on the stack are destroyed after going out of scope.

But you are ignoring that C ++ will use temporary objects. You have to find out when the temporary variable will be created (and then optimized) by the compiler for your code to work.

Temporary objects

Note that in what follows, I describe a very simplified "clean" point of view about what is happening: compilers can and will do optimizations, and among them useless temporary ones are removed ... But the behavior remains the same.

Whole?

Let's start slowly: what should happen when you play with integers:

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

      

The above code can 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 (call the copy constructor). And if you return from the function "by value", the compiler will make a temporary copy again.

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) ;
}

      

can be written as the following inline 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, even though you are not writing code, calling or returning from a function will (potentially) add a lot of "invisible code" required to transfer data from one level of the stack to the next / previous one.

Again, you need to remember that the C ++ compiler will optimize from time to time, so what can be viewed as an inefficient process is just an idea and nothing else.

About your code

Your code will leak: you are a "new" object and you don't delete it.

Despite your concerns, the correct code should look more like:

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

      

What's with the following code:

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

      

Will output the following pseudocode:

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 are doing too much time. Of course the compiler will probably remove them, but then there is no need to get into messy habits.

You should at least write your code like:

inline Point Point::operator+ (const Point & a)
{
   return Point(this->x+a.x,this->y+ a.y) ;
}

      

+3


source


You've already had some good answers. Here are a few more points that I would like to add to them:

  • You should avoid copying objects Point

    . Since they are more than built-in types (from your code I assume they are two built-in modules), copying them is more expensive on most architectures than passing them through each reference. This changes your statement to: Point Point::operator+ (Point

    &

    )

    (note that you need to copy the result as there is no place to store it permanently, so you can pass a reference to it.)
  • However, to check the compiler, you do not mess it up and do not accidentally change the operator argument, you pass it over the const

    link: Point Point::operator+ (

    const

    Point&)

    .
  • Since operator+()

    (except, for example operator+=()

    ) doesn't change its left argument either, you should check the compiler as well. For a binary operator that is a member function, the left argument indicates what the pointer is pointing to this

    . To make a this

    constant in a member function, you must type const

    at the end of the member function signature. It does this: Point Point::operator+ (const Point&)

    const

    . Now your operator is what is commonly called const-correct.
  • Usually when you provide operator+()

    for your type, people expect it operator+=()

    to be present as well, so usually you should implement both. Since they behave very similarly, in order not to be redundant, you must implement one on top of the other. The easiest and most efficient way (and therefore more or less canonical) is to implement +

    on top +=

    . This makes it easier to write operator+()

    and more importantly: it basically looks the same for all the types you implement for:

Since it operator+()

got pretty trivial, you probably want inline

it. Then this will be the resulting code:

 inline Point Point::operator+ (const Point& rhs) const
 {
    Point result(this);
    result += a;
    return result;
 }

      

These are a few basic syntactic and semantic features that (hopefully) everyone will agree on. Now, here is a rule of thumb that I use for my code, which I find very useful, but which probably not everyone will agree:

  • Binary operators that handle both of their arguments the same (which usually means they don't change either of them) should be implemented as free functions, binary operators that handle their left argument (usually: this change) should be implemented as member functions.

The reason for the latter (take operator+=()

as an example) is pretty straight forward: to change it, they might need to access the internal arguments of the left argument. And changing the properties of an object's object is best done through member functions.

The reasons for the former are not so simple. Among other things , Scott Meyers had a great article explaining that, contrary to popular belief, using non-member functions often actually increases encapsulation. But then also the fact that for the argument this

of member functions some rules (implicit conversions, dynamic dispatch, etc.) are different from the rules for other arguments. Since you want both arguments to be treated the same way, in some cases it might seem surprising that different rules apply to the left side.

Then the code looks like this:

 inline Point operator+ (const Point& lhs, const Point& rhs) const
 {
    Point result(lhs);
    result += rhs;
    return result;
 }

      

For me, this is the ultimate canonical form that I write in my code without thinking about it, no matter what type it is.

The implementation is operator+=()

left as an exercise for the reader.:)

+2


source


Data and code are orthogonal concepts. What's the difference that Code is working on an object from the heap and not one on the stack? (make sure you respect the object's scope in both cases)

0


source


You are correct that the data on the stack is unusable when executing a function. However, it's perfectly okay to push copies of data onto the stack (that's what you do). Just make sure you don't return pointers to data on the stack.

0


source


Using your code:

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

      

This will work fine.
It basically creates the result locally (on the stack). But the return statement copies the result back to the calling point (just like an int). It uses the dot copy constructor to copy the value back to the calling point.

int main()
{
    Point  a(1,2);
    Point  b(2,3);

    Point  c(a + b);
}

      

Here the + operator creates a local one on the stack. This is copied back to the calling point (constructor for c) on return. Then the copy constructor for c is used to copy the content to c.

But you think it's a little expensive to build a copy. Technically yes. But the compiler is allowed to optimize additional copies (and all modern compilers are very good at it).

Back to your code.

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

      

Do not do this. Here you have assigned dynamically, but you are copying the result back to the call point (as described above using the copy constructor). Thus, by the time control returns to the point of the call, you have lost the pointer and cannot allocate the memory allocation (thus a memory leak).

The difference between Java and C ++ is that when we return pointers, we use smart pointers to help the caller determine who is responsible for freeing the memory (find the pointer reference).

0


source







All Articles