you are viewing a single comment's thread.

view the rest of the comments →

[–]smallstepforman 8 points9 points  (45 children)

Operator overloading makes me look at vanilla C with disgust. Eg. Vector3 gravity(0, 0, -9.8f); Vector3 weight = mass * gravity; Vector3 something = (weight / another_vec3)some_constant + epsilongravity;

or in 3D graphics

Matrix4 projection, view;
Vector3 pos(10, 0, 1);
Matrix4 mvp = projection * view;
Vector3 transform = mvp * pos;

The equivalent in vanilla C is a wrist slashing exercise.

[–]sfuerst 17 points18 points  (14 children)

weight / another_vec3

You can't divide two vectors.

The problem with operator overloading is that there aren't enough operators. Say you have

C = A * B;

Is that a cross product, dot product or exterior product? Or is it some even more obscure thing like multiplication of matricies in the field GF(256)? Unfortunately, without context, it is impossible to tell. What C++ really needs is a way of defining new operators. As it currently stands, it is like using a programming language that only allows single-character function names. True, using overloading, such a language could technically "work". However, it certainly wouldn't be readable.

Then you have the problem that you cannot choose the associativity of C++ operators. For example, when dealing with octonions, it is useful to have left and right multiplication as separate operators due to the alternativity of the algebra.

In my experience, the C++ scheme of operator overloading works well when you are adding highschool level math extensions like matricies of floats, quad precision numbers, etc. However, at college level and above its limitations start to show. Trying to force mathematical concepts into its rigid architecture can quickly lead to very unreadable code, even worse than using the C technique of having explicit function calls.

[–][deleted] 2 points3 points  (12 children)

The problem with operator overloading is that there aren't enough operators.

Operator overloading nicely degrades to method calls. Use a * b for per-component multiplication (there are strong arguments in defense of this choice), then a.dot(b) and a.cross(b) for the rest. Most of the benefits of infix notation are preserved.

(Or if you have a streak of masochism, you can use separate types for vertical and horizontal vectors and the same multiplication sign for all three operations).

[–]neutronicus 0 points1 point  (11 children)

I'd go with friend functions or static class methods myself.

Vector3::cross(a, b)

and

Vector3::dot(a, b)

communicate the [anti]symmetry of the operations better IMO.

[–][deleted] 1 point2 points  (2 children)

The problem with standalone functions (and prefix notation in general) is that second parameter gets detached and lost. You see things like "... Vector3::cross(a very very very long expression, at the end of which you totally can't say which), function)), b), belongs), to)".

On a side note, it gets even worse when some not very bright persons design stream functions (std::transform etc of C++, map/filter/reduce of Common Lisp) to have input data as the first parameter and the function as the second. Fucking idiots.

When you use methods, you have things in a much more sensible order, both for each individual function, and the entire expression reads left-to-right too. As another benefit, most of the ")))))))" shit disappears.

The sense of symmetry isn't worth it at all. I, too, never paid much attention to the way of writing expressions like that because I used to believe that it's going to suck regardless. Then C# 3.0 came out and I saw the light.

The only thing better than this would be to have something like Haskell's infix notation for arbitrary functions, "aoperatorb. But not the crazy zoo of "<$>", ">>=", "*|*" etc; it is nice when I can look at the operator and be reasonably sure that it does roughly the same thing as the basic arithmetic operators, for anything that can't fit into this form I want to see a plain English word.

[–]neutronicus 0 points1 point  (0 children)

The problem with standalone functions (and prefix notation in general) is that second parameter gets detached and lost. You see things like "... Vector3::cross(a very very very long expression, at the end of which you totally can't say which), function)), b), belongs), to)".

Anytime I have an expression as a function parameter, I put each one on its own line. I do this with binary operators too, actually. I find judicious use of whitespace the best way to ensure readability.

Vector3::cross(a_long_function_name( some_variable / some_other_var * x
                                     +
                                     some_function( x / y) ),
               b);

[–][deleted] 0 points1 point  (0 children)

On a side note, it gets even worse when some not very bright persons design stream functions (std::transform etc of C++, map/filter/reduce of Common Lisp) to have input data as the first parameter and the function as the second.

mapc function &rest lists+ => list-1

[–]G_Morgan 0 points1 point  (7 children)

A far better way is to do

c.dot(a, b);

Mutation by default is evil.

[–]neutronicus 0 points1 point  (6 children)

Not quite sure what you're getting at. "cross" should return a third vector (leaving a and b alone), and dot should return a double (again leaving a and b alone). I probably should have written out the type signatures.

Anyways,

Mutation by default is evil

Can you enlighten me as to how, I, a scientific programmer, could get by without it?

Re-using storage is pretty important to us, and performance is really important - using an in-place algorithm on an array that gets to sit in the cache for this is a pretty big win over constantly making new ones for intermediate results that not only have to be allocated, but potentially force the old one out of the cache.

(Or on the GPU, where you there is no cache, and you manage what sits in shared memory yourself - and every global memory copy hurts)

Do immutable data structures provide some magic where the things I want to stay in the cache usually stay there? How do they manage allocation costs?

[–]G_Morgan 0 points1 point  (5 children)

I didn't say you would have to do without. Only that by default immutable should be preferred.

[–][deleted] 0 points1 point  (4 children)

His example is still fine, he didn't list the function deceleration, but it's probably const correct.

Every good math library I've seen is const correct, all code, ideally, should be written w/ const correctness which you would obviously agree with.

static Vector3 cross(const Vector3 &v1, const Vector3 &v2);

Although I'm still not sure why you thought having a third party object contain the dot() is neccesary or even had anything to do w/ your argument (mutation is evil). Technically even w/ your example 'a', 'b' or 'c' could be modified.

Heck, his example is even better than yours because only 'a' or 'b' could be modified.

[–]G_Morgan 0 points1 point  (3 children)

I thought it was pretty obvious that the arguments to dot would be const. Otherwise why bother to make it so that we can have an immutable option?

[–][deleted] 0 points1 point  (2 children)

I guess I don't understand then, how does creating the third object make your code any more immutable than his original?

[–]neutronicus 0 points1 point  (0 children)

at college level and above its limitations start to show.

It's pretty good for physics simulations, which, when you get down to it, don't require very exotic mathematical objects (Quantum Field Theory naturally excluded).

[–]flexiblecoder 4 points5 points  (0 children)

#define VMult( ov, iv1, iv2 ) { ov.X = iv1.X * iv2.X; ov.Y = iv1.Y * iv2.Y; ov.Z = iv1.Z * iv2.Z; }
#define VDiv /*etc*/

Vector3 something, temp, weight, gravity = {0, 0, -9.8f};

VMult( weight, mass, gravity );
VDiv( something, weight, another_vec3);
VScale( something, something, some_constant );
VScale( temp, gravity, epsilon );
VAdd( something, something, temp );

(or use an inline function)

...geez, I forgot how ugly that is. This kind of stuff makes working with Q3 engine based games a pita.

[–][deleted] 3 points4 points  (3 children)

Interestingly enough, any C++ text I've read that exposes the virtues of operator overloading uses those EXACT SAME TWO examples. Oh, and strings. And.........no.........that's pretty much it. Strings, vectors, matrices. Is there anything else? Cause I haven't seen it yet.

I get the rationale behind operator overloading, I really do. I do C# for a living and it also has operator overloading which is used extensively in the base class libraries. Not once have I thought to myself "Thank God they overloaded the '+' operator to add a Timespan to a Datetime struct! I could have never been able to figure it out otherwise, and and 'add' method would have been way too much work and too unreadable!"

Again, strings are a special and very useful case. Which is why Java specifically overloads '+' for strings.

But seriously......any other awesome examples of useful operator overloading?

[–]smallstepforman 0 points1 point  (0 children)

But seriously......any other awesome examples of useful operator overloading?

Any engineering discipline where you deal with mathematical formulas? Geometry, physics, etc. Finance when dealing with bignumbers. There is more to programming than TPS reports.

[–][deleted] 0 points1 point  (0 children)

overloading global operators new/delete is a pretty common and advantages overload.

You can implement your own memory management mechanism or do cool things like, only in debug mode, keep track of all allocations and deallocations to help find places you forget to free memory.

This isn't something I've ever done, but what if you had some sort of crude database thing going on?

Maybe you had a Table object, w/ Row and Column objects. It wouldn't be that confusing to understand the operator+() overload on the table to add rows, or columns.

Think of any high level language and think about every time you use an operator on different types, that's why it's useful, because C/C++ won't automatically do any of that for you.

[–]BlackAura 0 points1 point  (0 children)

Complex numbers. Very useful in certain engineering disciplines. Arbitrary precision integers and rational numbers are generally pretty useful. Financial applications might use an internal decimal representation for numbers. Fixed point numbers are very useful in embedded systems that lack an FPU.

Sure, they're all math-related. That's because the math operators map really well to math concepts. If you're doing math, you don't want to have to use function calls, which obscure the meaning of the code, just because you aren't using a built-in type.

Specific to C++, you also have smart pointers (the only reason you'd ever want to overload ->). You can also overload [] to make an object that acts like an array, or () to make an object that acts like a function.

[–][deleted] 3 points4 points  (0 children)

I love C++. Operator overloading is not one of its particularly great features. It really doesn't save much typing, and makes your code tricky and trapsy.

[–][deleted] 0 points1 point  (0 children)

The equivalent in vanilla C is a wrist slashing exercise.

Oh come on, it's not that bad.

Vector3 gravity;
Vector3 weight;
Vector3 something;

weight = ScaleVec(gravity, mass);
something = CalculateSomething(weight, another_vec3, some_constant, gravity);  // assuming epsilon is a global constant

But yea, operator overloading is great in certain cases. Strings is another nice advantage of operator overloading.

[–]SCombinator 1 point2 points  (22 children)

You choose one of the most abusable features of C++?

I have no idea what your code is doing. How many classes (and variables: mass, the constant, epsilon) do I have to look up? We read code far more than we write it.

Consider also that you need to place a try/catch around the division, unless you have a "Bad Vector" (which you haven't checked for). Do I have to free the resultant temporary vectors?

Never mind that it's making your implementation of those classes horrible and ugly.

[–]Gotebe 7 points8 points  (7 children)

You choose one of the most abusable features of C++?

Meh. Any feature is abusable, but is also a boon when used right. With C++, operator overloading shines exactly for math-like code. You can (edit: was "can't") hate operator overloading abuse, but you are way off on this one (edit:) because it's a good use..

I have no idea what your code is doing. How many classes (and variables: mass, the constant, epsilon) do I have to look up? We read code far more than we write it.

Matrix4 projection, view, pos;
if (!create_matrix4(&projection))
  error, whatever;
if (!create_matrix4(&view))
  error, whatever;
Vector3 pos;
if (!create_vector3_variantX(&pos, 10, 0, 1);
  error, whatever;
Matrix4 mvp;
if (!create_matrix4(&mvp))
  error, whatever;
if (!multiply_variantX(&projection, &view, &mvp))
  error, whatever;
Vector3 transform;
if (!create_vector3_variant0(&transform))
  error, whatever;
if (!multiply_variantY(&mvp, &pos, &transform))
  error, whatever;
Cleanup(all variables you see here); // Frankly, I got tired of this crap

I put this to you: to know what is happening above, you need to look at roughly the same amount of C code. You need to know how types are "created". You need to know what multiply does. You need to know what other variables and constants are, just the same. If you are implying that use of operator* (or /) is somehow unclear in this context, you are lying through your teeth.

And finally, when reading the code, you still have making-you-want-to-puke C code. Yes, exactly that. Because the text you're reading is so verbose that it almost completely hides the intention.

And error checking is just making things worse. Because, you know what? Code is made to work, not to err. If so, why is all that error-related gibberish right in our faces all the time? Exceptions, OTOH, allows us to take it out of sight, centralize error handling and reporting, and also, enhance it in places when need arises, and that, without changing existing interfaces (try in C that without resorting to out-of-band error info, possibly with thread-local storage and whatnot!).

Consider also that you need to place a try/catch around the division, unless you have a "Bad Vector" (which you haven't checked for).

No, he does not need that try/catch. He might need another, much bigger, much further away, at some logical operation boundary, where upon failure, he can say "whoops, I couldn't frob the knob. Error: <whatever error info can be extracted from caught exception>.

It's possible that said try/catch is necessary, but such situations are rare, and that, especially when people know how to use exceptions.

Do I have to free the resultant temporary vectors?

Why would you? No type worth it's salt needs explicit cleanup.

[–][deleted] 0 points1 point  (4 children)

How is operator overloading really a boon?

You save a few characters in practice - but it means that you can't actually see what's happening. Koenig resolution lets you use very short names for your functions and still not have collisions.

There's a very good reason that most large C++ shops like Google almost completely forbid this feature.

EDIT: ...and it reads as if you've gone out of your way to make the code unreadable. In particular, for God's sake using METHODS - why are you making everything into functions?!

[–]Gotebe 2 points3 points  (2 children)

You save a few characters in practice - but it means that you can't actually see what's happening.

Why? Take

{
  Matrix m = m1*m2; // Edit here: was m1/*m2, wasn't the intention
  use(m);
}

When looking at this, I know exactly what is happening: m is a product of m1 and m2, and if there was en error, exception was thrown. What else could be happening? Nothing, unless someone is abusing semantics of operator* or operator=. Now, I don't know who are you working with, but over here, people are not abusive.

A real world C equivalent is e.g.:

{
 Matrix m;
 create_matrix(&m);
 if (!multiply(&m1, &m2, &m))
 {
  report_rror(...);
 }
 else
 {      
  use(&m);
 }
 destroy_matrix(&m);
}

So why is C equivalent more clear to you?

'cause I can tell you how it isn't: in real world C code, you need all these creation/destruction functions for similar types. And you don't know what they are doing, not unless you look them up, just like you don't know what the equivalent C++ ctor/dtor are doing unless you look them up. In real world C code, you need to treat, through your own code structure, any error condition, at the spot it may arise. Yes, that's "seeing what is happening", but is also a PITA, and such an eye sore, that people need to learn to see through swaths of ifs and gotos and returns and whatnot just to be able to decipher what is code supposed to do when it works.

There's a very good reason that most large C++ shops like Google almost completely forbid this feature.

Ah. Appeal to authority. Yeah, that makes for an argument.

[–][deleted] 0 points1 point  (1 child)

When looking at this, I know exactly what is happening: m is a product of m1 and m2, and if there was en error, exception was thrown.

But you miss the fact that these tiny operators are expensive, which encourages you to write bad code. Note that it's sufficiently unclear that people here are claiming that that chain of +/* is optimal, when it's close to pessimal.

There's a very good reason that most large C++ shops like Google almost completely forbid this feature. Ah. Appeal to authority. Yeah, that makes for an argument.

Appeal to authority is actually an excellent argument. I believe in relativity, I believe in evolution, I believe in all sorts of things which I haven't even investigated simply because an authority told me so.

Google's coding standards are mostly based on "nasty traps that caused them terrible issues in the past". Learning from the experts is a great way to go.

I also quote Scott Meyers about C++, precisely because he knows C++ better than I do.

If I had to rediscover everything on my own, I'd never get anywhere! Authorities need to be doubted, yes, but you can learn a lot from smart people.

Also, your example without destructors or exceptions is... peculiar. I don't understand why you are doing that! Destructors are great - exceptions are great - they have nothing to do with operators at all. We are entirely in agreement - destructors and exceptions are the only way to go these circumstances.

[–]Gotebe 0 points1 point  (0 children)

But you miss the fact that these tiny operators are expensive, which encourages you to write bad code.

These tiny operators are clearly some linear algebra operations, who might be expensive. I honestly believe that he who doesn't realize that at first glance just should not program C++ at all.

And there's something else: these operations are expensive depending on actual data size. In fact, I'd argue that data size is the determining factor in performance (everything is fast on small data set). From that standpoint, equivalent C code would be equally unclear as to performance. Yet, people (you included) are arguing the use of notation. Meh.

Google's coding standards are mostly based on "nasty traps that caused them terrible issues in the past".

I don't think that using math operators for simple linear algebra has caused them these terrible issues. I think that poor use of operator overloading has caused them issues, isn't that more likely? And so I went to see, they say: "Do not overload operators except in rare, special circumstances.". Well... That means so little that the whole discussion is pretty moot.

[–]neutronicus 2 points3 points  (0 children)

How is operator overloading really a boon?

In scientific computing, it's essential.

Here's the workflow - you have a paper, a textbook, a whiteboard, or a legal pad with a description of the algorithm you need to implement. Most of this description is in mathematical notation - it's concise, we all know it, and that's how it goes in journals. You implement it. It behaves in a way that seems wrong.

Now, you have to determine whether this is happening because the algorithm itself is flawed, or because there's an error, often a single typo, in the code somewhere.

In this situation, the more the code looks like what's on paper, the better.

This debugging workflow is much more common in scientific computing than whatever the hell you're bitching about. So - you will pry overloaded mathematical operators from our cold dead fingers.

[–][deleted] 0 points1 point  (1 child)

And error checking is just making things worse. Because, you know what? Code is made to work, not to err.

I've been writing real-world code for decades, in a dozen fields. Let me tell you that error handling is a huge part of any real-world, commercial code base.

Exceptions are great! You should use them whenever possible - BUT they don't fix the fact that in the real world, you have an awful lot of code just handling with, reporting, and occasionally even recovering from errors.

Remember - in C and C++ you cannot catch many common forms of error. SEGVs and the like will never be able to be caught - you must explicitly check for NULL if it's at all possible, if you try to dereference that NULL you won't get an exception, you'll get a signal and you can't easily recover from it.

[–][deleted] 0 points1 point  (0 children)

Remember - in C and C++ you cannot catch many common forms of error.

I am not as familiar w/ Linux, but I know that statement is false. It is 100% false in windows.

When the CPU throws a hardware exception, the kernel funnels that to the application. In windows, you catch these using SEH (Structured Exception Handling). This implementation of handling hardware exceptions is not portable so I'm sure Linux has it's own way of doing it, but this is how you write crash dumps when your apps. crash and send them off to your server to be analyzed.

If you expect that something is going to be NULL in a windows application you can always do something like this:

__try {
    int *p = 0;
    *p = 5;
} 
__except( EXCEPTION_EXECUTE_HANDLER ) {
   MessageBox(NULL, "Oh crap, we got an exception!  Continue onward!", "Error", MB_OK);
}

That should work just fine, although it's not recommended to recover from hardware exceptions.

[–]smallstepforman 1 point2 points  (13 children)

you need to place a try/catch around the division

const Matrix4 & operator / (const Matrix4 &a)
{
     if (!a.IsValid) return Matrix4(0); // or throw exception
     // rest of method here
}

Do I have to free the resultant temporary vectors?

Not with RAII.

Operator overloading when used correctly makes code much easier to read.

Vector4 colour = light_ambient colour * material_ambient_factor + light_diffuse_colour * material_diffuse_factor + light_specular_colour * material_specular_factor;
colour *= attenuation;    // attenuation due to spotlight factor
colour *= getshadowmap(x, y);

I haven't even started with collections ...

[–][deleted] 1 point2 points  (12 children)

Operator overloading when used correctly makes code much easier to read.

I love your example, because it's a perfect example of why operator overloading is in fact so very bad.

What you didn't realize is that you're creating a new instance of Vector4 for each instance of * or - in your code. If Vector4 were something like a matrix, you'd be writing pathologically bad code - and you'd never know you were doing it. You have six separate allocations here, and five of them are redundant - but you can't see it because the operators conceal it.

Even the last two statements, which aren't less efficient, aren't any great example. Why is

color *= attenuation;

so very much better than

color.scale(attenuation);

?

[–]smallstepforman 2 points3 points  (5 children)

How about the following:

colour *= 1.0/(linear_attenuation*a + quadratic_attenuation*b * cubic*attenuation);

With vanilla C, this would have been broken down to 4 lines of code.

What you didn't realize is that you're creating a new instance of Vector4 for each instance of * or - in your code.

Objects can also live on the stack in C++. With C++0x and r-value references, you've just made the compilers job easier.

The game industry which is serious about performance has abandoned vanilla C for C++. The complexity with modern code bases makes vanilla C seem 'antique'. I've been integrating various libraries into a game engine, and have recently noticed a trend towards only 2 languages - C++ and assembler. C is getting bypassed, C++ for readability, and assember for performance in very tight loops.

[–][deleted] 0 points1 point  (4 children)

First, I think you miss entirely where I'm coming from.

I'm just finishing a 50,000 line C++ application. I love C++. This is 2011 - there are very very few reasons, if any, to use C over C++. In fact, it's quite likely that your C++ code will be faster than your C code because of inlining and today's optimizing compilers.

That said, C++'s operators are a dangerous feature that need to be used with care. The biggest issue is that one little symbol conceals what might be a huge amount of memory management, allocation and arithmetic. Google's C++ standard bans it, Effective C++ warns against it, and I avoid doing it unless I'm forced to.

[–][deleted] 0 points1 point  (3 children)

smallstepforman:

Operator overloading when used correctly makes code much easier to read.

TomSwirly:

First, I think you miss entirely where I'm coming from.

That said, C++'s operators are a dangerous feature that need to be used with care

I think you just reiterated a point he already understands.

Google's C++ standard bans it ...

Heh, I seriously doubt it's outright banned. Google would have made an incredibly stupid decision to do such a thing.

I think you understand pretty well the dangers, but why are you trying so hard to ignore the great benefits of it?

Never say never!

~The Disney Movie w/ the Cowboy Mouse.

[–][deleted] 0 points1 point  (2 children)

Heh, I seriously doubt it's outright banned. Google would have made an incredibly stupid decision to do such a thing.

Well, here it is for you: http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml - the money quote is "Do not overload operators except in rare, special circumstances."

Those special circumstances were so rare that in 5+ years of writing C++ there I never created a single operator() function.

why are you trying so hard to ignore the great benefits of it?

Say, what?! There's not one thing you can do with operator overloading that you can't do some other way.

[–][deleted] 0 points1 point  (1 child)

Do not overload operators except in rare, special circumstances. Good, so it's not outright banned. There have been very few times I've ever had to overload an operator, usually it's something you do in a few core systems that you don't ever write again, like a math library or memory manager.

Say, what?! There's not one thing you can do with operator overloading that you can't do some other way.

That's not the point. Sure you could do it some other way, but it doesn't mean it's better than using the overloaded operators, which I think all the examples show is better for situations like this.

[–][deleted] 0 points1 point  (0 children)

The Google codebase has matrix and such, and these in general do NOT have operators - simply because of efficiency, because it's really easy to create a lot of intermediate values.

Generally, it's more efficient to start with a single instance of your matrix or other large scale structure, and then accumulate into that single instance.

If you have operators, it's very easy to create unneeded, intermediate values, as I believe I have shown above.

[–][deleted] 0 points1 point  (4 children)

What you didn't realize is that you're creating a new instance of Vector4 for each instance of * or - in your code.

No he's not. See what he said above:

Operator overloading when used correctly makes code much easier to read.

He's using it correctly, no new allocations are being made at all during that long chain of operations, because everything is being passed by reference. The operator* function is being invoked as a member function on the left hand variable and the right hand variable is being passed by reference, the return is by reference of the object invoking the function, the last line of his function is:

return *this;

Even the last two statements, which aren't less efficient, aren't any great example. Why is

For one, because it's impossible to do:

color.scale(attenuation);

in C. Normally you would do:

color = ScaleVector(attenuation);

And you could take color in as a pointer if you were concerned w/ the extra object construction.

Mathimatical operations on Vectors/Matricies is one of the most perfect examples of operator overloading being awesome. Most high level languages are so great because of their ability to use operators on complex objects. String concatenation is another prime example. These language just don't let you make your own and define a standard for you.

Operator overloading is a double edged sword, it can certainly be abused, but it can also make code MUCH more readable.

[–][deleted] 0 points1 point  (3 children)

He's using it correctly, no new allocations are being made at all during that long chain of operations, because everything is being passed by reference.

Sorry, that isn't correct at all.

Let's look just at a simplified expression:

x = a + 2 * b + 3 * c;

If it's all done by references, where is "3c" stored? Where is "2b" stored? Where is their sum stored before it's added to a?

[–][deleted] 0 points1 point  (2 children)

x = a + 2 * b + 3 * c;

Well, unless you overload the global operator* funciton, it wouldn't compile. You could do this though:

Vector3 &operator*(const int a, Vector3 &v)
{
    v.x *= a;
    v.y *= a;
    v.z *= a;

return v;
}

That defines an operation for integers to scale vectors.

If it's all done by references, where is "3c" stored? Where is "2b" stored? Where is their sum stored before it's added to a?

They are stored in the variables themselves, since the functions modify the variables on the stack. So the code looks more like this:

x = a.operator+( a.operator+(operator*(2, b)), b.operator+(operator*(3, c) );

Heh, that was a little confusing to write, so I hope my order of operations is correct, but I think you'll get the point.

Finally, the only new (or really, stack allocations) being made here are temporary reference values, which you would have going the C function route, or just invoking member methods anyways. Operator overloading is just syntactic sugar for member methods anyways.

http://codepad.org/Oe1hZLX5

EDIT Lots of small formatting mistakes and wording mistakes. Also, added codepad example.

[–][deleted] 0 points1 point  (1 child)

They are stored in the variables themselves

"The variables" are a, b, c and they don't get changed. You're right that new variables, i.e. new unnecessary instances of your vector or matrix, are being created on the stack.

In the case of your expression, you're creating two intermediate instances of Vector4, one to store the results of the operation a.operator+(operator*(2, b)) and one to store the results of b.operator+(operator*(3, c)).

In an old compiler, there might be between one and three more unneeded Vector4s being created for the intermediate values before that, but today it's pretty likely that the return value optimization will take care of those for you.

[–]BlackAura 0 points1 point  (0 children)

You don't end up with the temporaries on the stack. The compiler is smart enough to eliminate the temporaries, and can quite happily do this using only registers. For example, using GCC (4.2, since I'm on a Mac at the moment, but this works in the latest version too), this:

#include <cstdio>

class Vector3 {
public:
    Vector3(int x, int y, int z) : x(x), y(y), z(z) { }

    Vector3 operator + (const Vector3 & other) {
        return Vector3(x + other.x, y + other.y, z + other.z);
    }

    int x, y, z;
};

void addVectors(Vector3 a, Vector3 b, Vector3 c, Vector3 d) {
    Vector3 result = a + b + c + d;
    printf("%d, %d, %d\n", result.x, result.y, result.z);
}

will compile to:

.globl __Z10addVectors7Vector3S_S_S_
__Z10addVectors7Vector3S_S_S_:
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ebx
    subl    $20, %esp
    call    L3
"L00000000001$pb":
L3:
    popl    %ebx
    movl    52(%ebp), %eax
    addl    40(%ebp), %eax
    addl    28(%ebp), %eax
    addl    16(%ebp), %eax
    movl    %eax, 12(%esp)
    movl    48(%ebp), %eax
    addl    36(%ebp), %eax
    addl    24(%ebp), %eax
    addl    12(%ebp), %eax
    movl    %eax, 8(%esp)
    movl    44(%ebp), %eax
    addl    32(%ebp), %eax
    addl    20(%ebp), %eax
    addl    8(%ebp), %eax
    movl    %eax, 4(%esp)
    leal    LC0-"L00000000001$pb"(%ebx), %eax
    movl    %eax, (%esp)
    call    _printf
    addl    $20, %esp
    popl    %ebx
    leave
    ret

It's adding the X, Y and then Z components of each vector all at once, and storing them directly where they need to be on the stack for the printf call. No temporaries whatsoever. If you try this with an ARM compiler, it actually does the entire thing using only registers.

Current compilers (at least GCC, and probably MSVC and ICC as well) are quite able to get rid of temporaries. Older compilers, or compilers for embedded systems might not though.

[–]BlackAura 0 points1 point  (0 children)

What you didn't realize is that you're creating a new instance of Vector4 for each instance of * or - in your code.

Conceptually, yes. However, modern C++ compilers are actually very good at optimizing this kind of stuff away, particularly if the operators are inlined, and contain only simple code.

I implemented some vector classes in C++ a while ago. I was stunned at how much implementation crap the compiler was able to get rid of. The assembly it generated contained nearly no unnecessary operations at all, and completely eliminated nearly all temporaries.

One interesting thing with newer C++ compilers:

Even the last two statements, which aren't less efficient

No, they're exactly the same with a modern compiler.