Sorry your browser is not supported!

You are using an outdated browser that does not support modern web technologies, in order to use this site please update to a new browser.

Browsers supported include Chrome, FireFox, Safari, Opera, Internet Explorer 10+ or Microsoft Edge.

Dark GDK / [Tutorial] Overloading Operators

Author
Message
TheComet
16
Years of Service
User Offline
Joined: 18th Oct 2007
Location: I`m under ur bridge eating ur goatz.
Posted: 30th Aug 2012 21:08 Edited at: 1st Sep 2012 16:17
So since I'm learning about C++ from this nifty book I bought I thought I'd share a few tips here and there. Here I'm going to explain the basics of overloading operators and how it's useful along with some examples. Since you're using a C++ compiler for DarkGDK this will of course work with DarkGDK as well.

First of all, why would anyone want to overload an operator? You'll be able to add more functionality to the usual operators found in C++ (such as + - * / && << >> || etc.). You'd be able to change this:



to this:



Section 1

Here's a small example. Let's assume you have some vector calculations you want to do:



This is a simple example of adding 2 vectors together, which is something that happens a lot in 3D games. In particular look at this section:



Wow, that's a bit silly if we have to do that for every vector in the code... This is where overloading becomes handy. Usually you'll see people create some kind of function to solve the problem, perhaps something like this:



But that's still not the cool way of doing it. Now you could argue that you could make the function inline and all of that to speed things up, but that just complicates things.

The solution? We are going to overload the + (plus) operator. First step to doing this is declaring it in the class like this:



Note that we used an & symbol in front of "e". This is something only C++ can do and is known as a "reference", meaning that we're not actually using the data of e, but e is a reference to the object passed. We're changing the actual data of the object, not a copy of it. I guess I could go deeper into it but that's something for another time. Just trust me that it should be there.

You'll also notice that I've declared the returned data as const vec3, e as a const vec3& and the method as const. This is, respectively, so the returned data can't be altered (prevents things like result + myVector1 = myVector1 + myVector2), referenced data cannot be altered within the method, and the method cannot alter it's own data within the object.

So let's look closer at that new line:

vec3 operator+( vec3 &e );

An operator overloaded function looks just like any other C++ function, with just one exception: the name of the function must be operator, followed by the operator you want to overload. In other words, you can use operator to overload any operator you want:

operator+();
operator-();
operator*();
operator/();
operator<<();
etc.

Just like functions, the vec3 at the beginning means that it returns a data type of "vec3". Since we're declaring the overload inside the class, the left argument is already given (namely the contents of the class), which means that it only requires the right argument to be complete, which is vec3 &e.

Next step, naturally, is to define the function. Here it is:



That looks much like the function above I just told you not to use. In fact, it's exactly the same code except that the function is now called operator+. The reason why will become apparent in this next piece of code. I don't think the above needs explaining, as it's the standard way of defining functions.

The plus operator is now overloaded. So what the hell did all of that just do? Well, instead of this:



We can now simply write this:



Awesome, right? Here's the entire code for that:



Important: The + (plus) operator is still able to do normal additions. The only time when the overloaded version of it becomes active is when there are two data types of vec3 on both sides of it (such as "myVector1 + myVector2"). Otherwise it still performs as normal.

You can also add multiple vectors together no problem:




Section 2

So what else can we overload? Hmm, well THIS looks like a bit of work, doesn't it?



Let's overload << (insert operator) so we can just output our vectors directly like this:



So the first problem you're going to notice is that until now we've only overloaded operators where we had control of both sides (myVector1 + myVector2). So how do we overload an operator that has already been overloaded by another class? We weren't the ones that defined "cout", so how do we overload it again? (cout << myVector1). The answer is the keyword friend. Yes, C++ allows you to make friends with other classes. What does this do? It gives us access to the normally protected and private data of that class. Just an example demonstrating this:



See how Thief is able to get the private data anyway? This is possible because the function getPrivateData() was declared as a friend of the class.

So using this newly learned technique, let's apply that to our class so we can be friends with ostream (which has the declaration of cout in it). Here's the updated class:



So what does this do?

friend ostream &operator<<( ostream &out, vec3 &e );

We're telling the compiler that ostream is now a friend of our class, thus giving us access to it's private data. That allows us to use cout as the left argument of <<. Apart from that, we declare the overload function as usual:



And there you go! Now you can use << to output your vectors directly!

cout << myVector1 << endl;

Here's the entire code:




Outro

Keep in mind that this can be used with anything. It doesn't have to be vectors, this can be done to any class you declare.

I hope this was useful for you. This technique greatly simplifies your code and I urge you to use them instead of those ugly work-arounds with functions or #define statements.

TheComet

"if you don't understand recursion than you probably don't understand recursion." ~Jerico2day
puppyofkosh
17
Years of Service
User Offline
Joined: 9th Jan 2007
Location:
Posted: 30th Aug 2012 23:17 Edited at: 30th Aug 2012 23:18
Quote: "You'll be able to add more functionality to the usual operators found in C++ (such as + - * / && << >> || etc.)."


Some of those operators, namely && and || I wouldn't try to overload. This is because you'll replace the usual short-circuit evaluation rules with function-call evaluation rules. This can get nasty:



Since for Boolean operator|| is a function, all of the short-circuit rules that we normally follow for logical operators go out the window. In this case what happens is that func(p) is evaluated so a Boolean can be implicitly created with the single argument constructor, so it can "fit" the argument of const Boolean& for the operator|| call.

Even if we didn't have to worry about the implicit creation of a Boolean by declaring the constructor as explicit, and then having func return a Boolean, we'd still have this issue, because the operator|| argument, "o" would be evaluated (and so, func would be called).

I'd also just like to say that I think for your operator+ function, you should pass a const vec3&, not just a vec3& (and I'd also declare the function as const).
Neuro Fuzzy
16
Years of Service
User Offline
Joined: 11th Jun 2007
Location:
Posted: 31st Aug 2012 01:28
I noticed that you wrote operator<< instead of vec3::operator<<, so is it a global function or a member function? I usually go the global function route, didn't know about the friend function method.

Also, if you have a destructor, you also should worry about:

Object &operator=(const Object& arg);

Rudolpho
18
Years of Service
User Offline
Joined: 28th Dec 2005
Location: Sweden
Posted: 31st Aug 2012 03:29
Adding to the topic, operator[] is pretty nice to overload for homemade collection type objects.


"Why do programmers get Halloween and Christmas mixed up?"
TheComet
16
Years of Service
User Offline
Joined: 18th Oct 2007
Location: I`m under ur bridge eating ur goatz.
Posted: 31st Aug 2012 13:06
Quote: "I'd also just like to say that I think for your operator+ function, you should pass a const vec3&, not just a vec3& (and I'd also declare the function as const)."


Would you mind explaining further why you'd choose to do that?

I'm still trying to wrap my head around "const". I understand that it forbids the changing of data, but what benefits do you get for passing a const vec3& other than the fact that the method can't change it's data? Is that some kind of a failsafe thing so you don't accidentally alter the data of the passed object because it's referenced? The same question goes for defining the function as "vec vec3::operator+( vec3& e ) const".

Quote: "I noticed that you wrote operator<< instead of vec3::operator<<, so is it a global function or a member function? I usually go the global function route, didn't know about the friend function method."


Yeah, it's a global function. I don't have a compiler with me right now, but this would probably be the way to make it local : ostream vec3::&operator<<( ostream &out, vec3 &e )

Quote: "Also, if you have a destructor, you also should worry about:

Object &operator=(const Object& arg);"


Again, still new to all of this. Could you give some example code that shows the problem?

TheComet

"if you don't understand recursion than you probably don't understand recursion." ~Jerico2day
Dar13
15
Years of Service
User Offline
Joined: 12th May 2008
Location: Microsoft VisualStudio 2010 Professional
Posted: 31st Aug 2012 14:53
Quote: "I'm still trying to wrap my head around "const". I understand that it forbids the changing of data, but what benefits do you get for passing a const vec3& other than the fact that the method can't change it's data? Is that some kind of a failsafe thing so you don't accidentally alter the data of the passed object because it's referenced? The same question goes for defining the function as "vec vec3::operator+( vec3& e ) const"."

Const on a reference allows the compiler to do some special optimizations on that variable(and the entire function when the function is declared const).

TheComet
16
Years of Service
User Offline
Joined: 18th Oct 2007
Location: I`m under ur bridge eating ur goatz.
Posted: 31st Aug 2012 15:22
Quote: "Const on a reference allows the compiler to do some special optimizations on that variable"


According to my book all it does is make the passed variable "read only". For instance, this code here:



Would work, where this here would complain that it can't change e:



There's no talk about any kind of special optimisations at all.

Still not sure what happens when you do it to the function, still reading.

TheComet

"if you don't understand recursion than you probably don't understand recursion." ~Jerico2day
Dar13
15
Years of Service
User Offline
Joined: 12th May 2008
Location: Microsoft VisualStudio 2010 Professional
Posted: 31st Aug 2012 16:20
Quote: "According to my book all it does is make the passed variable "read only"."

Well yes, that's what the const keyword does. Just did some more research into the const optimizations, and for most(scratch that, almost all) cases there aren't any amazing optimizations that the compiler can do. However, passing by reference is definitely faster than passing by value and passing a const reference prevents dumb programmer mistakes(accidentally doing something like this:
). Passing in a const reference causes the compiler to look for silly mistakes like that.
For example,


Matty H
15
Years of Service
User Offline
Joined: 7th Oct 2008
Location: England
Posted: 31st Aug 2012 16:53
Also, it makes the user of the function aware that their data will not be altered, since references are sometimes used as a way to alter the data inside the function.

TheComet
16
Years of Service
User Offline
Joined: 18th Oct 2007
Location: I`m under ur bridge eating ur goatz.
Posted: 31st Aug 2012 17:27
OK, so what's the difference between these two?





TheComet

"if you don't understand recursion than you probably don't understand recursion." ~Jerico2day
Matty H
15
Years of Service
User Offline
Joined: 7th Oct 2008
Location: England
Posted: 31st Aug 2012 18:01 Edited at: 31st Aug 2012 18:03
Maybe try:



Your function with 'const' should catch this error.

Thanks for posting this stuff on operator overloading, I have never got around to using it, but I will use this thread as a guide when I add to my vector classes.

TheComet
16
Years of Service
User Offline
Joined: 18th Oct 2007
Location: I`m under ur bridge eating ur goatz.
Posted: 31st Aug 2012 18:32
Quote: "Power(r) = 64"


Isn't that supposed to be Power(r) == 64?

TheComet

"if you don't understand recursion than you probably don't understand recursion." ~Jerico2day
Matty H
15
Years of Service
User Offline
Joined: 7th Oct 2008
Location: England
Posted: 31st Aug 2012 18:42 Edited at: 31st Aug 2012 18:43
Yeah, exactly

The function returning a const would catch that for you.

Rudolpho
18
Years of Service
User Offline
Joined: 28th Dec 2005
Location: Sweden
Posted: 31st Aug 2012 20:45
Basically a const declaration preceeding anything means what you'd expect; the value cannot be changed.
The funky thing with those C++ keywords (mainly const and static) is that they have different meanings depending on where they apprear.
When writing classes you can put a const declaration after the standard member function prototype to indicate that the method will not alter the state of the object it is invoked on (see const correctness).

I'm not entirely certain but I think if you had something like this



"Why do programmers get Halloween and Christmas mixed up?"
TheComet
16
Years of Service
User Offline
Joined: 18th Oct 2007
Location: I`m under ur bridge eating ur goatz.
Posted: 31st Aug 2012 21:00 Edited at: 31st Aug 2012 21:02
@ Matty H - Even without it being a const you still get an error though.

I understand it now.

A const in front of the function means the value returned is constant and can't be changed. This doesn't really make sense and isn't used all to often. The only time it can be used is when the programmer wants to return data that isn't allowed to be changed, such as a referenced object. Example:

This works:



Output will be : 6

This doesn't work:



A const at the end of the function, or to be more C++-ish, at the end of the "method", means that said method can not change it's own data contained in the object. Example:

You can see that the compiler here has no problem with setting it's own data to 7:



Inserting a "const" after the method prevents this:



A const inserted before one of the passed arguments prevents that data from being changed as you said earlier. Example:

The method has no problem changing the passed data:



And here with the const inserted, it won't work:



In conclusion

Making the vec3 arguments and method a const is a good decision. I've edited the first post.

Now I have one final question. What are these two consts that I've highlighted?

const int*const Method3(const int*const&)const;

TheComet

"if you don't understand recursion than you probably don't understand recursion." ~Jerico2day
TheComet
16
Years of Service
User Offline
Joined: 18th Oct 2007
Location: I`m under ur bridge eating ur goatz.
Posted: 31st Aug 2012 21:08
@ Neuro Fuzzy

Quote: "Yeah, it's a global function. I don't have a compiler with me right now, but this would probably be the way to make it local : ostream vec3::&operator<<( ostream &out, vec3 &e )"


It seems I was completely wrong. I have no idea if it's global or not, sorry.

TheComet

"if you don't understand recursion than you probably don't understand recursion." ~Jerico2day
puppyofkosh
17
Years of Service
User Offline
Joined: 9th Jan 2007
Location:
Posted: 31st Aug 2012 21:11 Edited at: 31st Aug 2012 21:13
Quote: "Would you mind explaining further why you'd choose to do that?"



Quote: "Also, it makes the user of the function aware that their data will not be altered, since references are sometimes used as a way to alter the data inside the function.
"


This is the most important reason, for me at least. In my opinion, a good rule to follow is to use const whenever you can. There's a good example of something in the code you posted where you don't necessarily need to use const, but you could. This:



(Let's say you changed the argument to be const already)

But you're returning a non-const vec3 from this operator. That means stuff like this is allowed:



If you instead wrote your declaration as



The mistake above would give a compiler error.




Declaring a method const means that the method cannot change member variables in the class (with one exception I know of-when a member is declared mutable). This tells the compiler that it is okay to call this function on a const vec3. For example, with the current code you've provided, this would not work: (again, let's assume you've declared operator+ to take a const vec3&):



That's cause you're calling operator+ on a const vec3, in this case someVector. That's not allowed unless you declare operator+ to be const.




This is the rule of three In your case this isn't an issue because the vec3 destructor is empty. However, if your class had a member variable that was a pointer to something, and the class had the responsibility of allocating/deallocating that something, it should define a copy constructor and assignment operator so that instead of copying the pointer, you copy the actual data. Here's a quick example:



If you ran this code (and it compiled) it might crash at the cout << d->getData() line. If not, you'll probably see "Freeing memory at <some memory address>" twice, and then it will crash. To fix this, we have to implement a copy constructor that performs a "deep" copy of the data member. That might look like this:



Then you'd have to do something similar for the assignment operator.

EDIT: I missed the last two posts while writing this, some of it is redundant, sorry.
TheComet
16
Years of Service
User Offline
Joined: 18th Oct 2007
Location: I`m under ur bridge eating ur goatz.
Posted: 31st Aug 2012 21:19
Quote: "EDIT: I missed the last two posts while writing this, some of it is redundant, sorry. "


Hehe, looks like we both posted the same info at the same time or something. I edited what you said into the first post btw, thanks for helping out.

What do these consts do?

const int*const Method3(const int*const&)const;

TheComet

"if you don't understand recursion than you probably don't understand recursion." ~Jerico2day
puppyofkosh
17
Years of Service
User Offline
Joined: 9th Jan 2007
Location:
Posted: 1st Sep 2012 02:34
Quote: "What do these consts do?

const int*const Method3(const int*const&)const;"


Well the const int* const means that the function is returning a constant pointer to a constant int. The const you put in bold means that p is constant-it cannot point to anything except for what it was initialized to.

Quote: "const int*const&"


This is a reference to a constant pointer to a constant int. A good way to try to read crazy declarations with lot's of "consts" is by reading them right to left.
aerostudios
14
Years of Service
User Offline
Joined: 20th May 2009
Location: Oklahoma City OK (USA)
Posted: 9th Sep 2012 03:24
I'm not sure why one would use a method to create a const.
Dar13
15
Years of Service
User Offline
Joined: 12th May 2008
Location: Microsoft VisualStudio 2010 Professional
Posted: 9th Sep 2012 05:48
Quote: "I'm not sure why one would use a method to create a const."

Sure it does. It allows the member function to 'guarantee'(can't truly guarantee due to the const_cast) that the data won't be edited by the programmer. I've had to deal with a couple of those, one of them being a const reference to a texture and the other a const pointer to an AI construct. It would be best used for POD types, I would imagine.

Diggsey
17
Years of Service
User Offline
Joined: 24th Apr 2006
Location: On this web page.
Posted: 9th Sep 2012 12:58
The second "const" is pointless since the pointer is being returned by value, so can be assigned without a cast to a non-const variable.

[b]
TheComet
16
Years of Service
User Offline
Joined: 18th Oct 2007
Location: I`m under ur bridge eating ur goatz.
Posted: 10th Sep 2012 10:14
Are you referring to this one?

const vec3 operator+( const vec3 &e ) const;

TheComet

"Why geeks like computers: unzip, strip, touch, finger, grep, mount, fsck, more, yes, fsck, fsck, fsck, umount, sleep." - Unknown
Diggsey
17
Years of Service
User Offline
Joined: 24th Apr 2006
Location: On this web page.
Posted: 10th Sep 2012 15:51
No, in your last post you asked:

Quote: "What do these consts do?

const int*const Method3(const int*const&)const;"


The one in bold is pointless since you can't assign to the result of a method anyway (unless it returns a reference).

ie. Method3(<params> = <value> will obviously not work even without that const.

[b]

Login to post a reply

Server time is: 2024-03-29 15:04:54
Your offset time is: 2024-03-29 15:04:54