fp_unit:
This page gives answer to the idea of optimisation:
Page 4:
Overloading the addition operator (+).
Let's define a simple class to create complex numbers. We will also define a method to add up complex numbers. And we will implement a method to print the final results:
Code:
#include <iostream>
using namespace std;
class Complex
{
public:
Complex Add(const Complex& c) const
{
Complex temp;
temp.m_re = m_re + c.m_re; temp.m_im = m_im + c.m_im;
return temp;
}
void print() const
{
cout<<"("<<m_re<<" , "<<m_im<<")";
}
public:
Complex(double r, double i): m_re(r), m_im(i) {}
Complex() : m_re(0), m_im(0) {}
private:
double m_re, m_im;
};
int main()
{
Complex A(12.347, 15), B(-2, 5.45), Result;
Result = A.Add(B);
Result.print();
cout<<endl;
return 0;
}
Result = A.Add(B) doesn't look really neat. It would be nice if we could implement such a thing as
Result = A + B.
This is where operator overloading comes into play. There is an operator "+" defined for (lets say) integers, and floats and doubles. But now we need to define our own "+" for our Complex class. Since "+" already exists, we need to overload it, to adapt it to our special needs.
A new keyword: operator+
Basically we want to substitute
Result = A.Add(B) with
Result = A.operator+(B). If we can manage that, we can also write
Result = A+B. That's the bottom line of operator overloading. There is nothing much to it.
But there are some "rules of engagement". We will discuss them when it's the right time to do so. First lets implement our overloaded operator "+". We just need to substitute our original Add() code with operator+() code. So here is how the transformation looks like:
we're eager to find out if this new code actually works. Here is the complete code listing. You can copy it, paste it and run it:
Code:
#include <iostream>
using namespace std;
class Complex
{
public:
Complex operator+(const Complex& rhs) const
{
Complex temp;
temp.m_im = m_im + rhs.m_im;
temp.m_re = m_re + rhs.m_re;
return temp;
}
Complex Add(const Complex& c) const
{
Complex temp;
temp.m_re=m_re+c.m_re;
temp.m_im=m_im+c.m_im;
return temp;
}
void print() const
{
cout<<"("<<m_re<<" , "<<m_im<<")";
}
public:
Complex(double r, double i): m_re(r), m_im(i) {}
Complex() : m_re(0), m_im(0) {}
private:
double m_re, m_im;
};
int main()
{
Complex A(12.347, 15), B(-2, 5.45), Result;
//line below: basically we just execute Result=A.operator+(B);
Result=A+B;
Result.print();
cout<<endl;
Result = A.Add(B);
Result.print();
cout<<endl;
return 0;
}
Efficiency and neatness
The line
Complex temp makes a call to the default constructor Complex() : m_re(0), m_im(0) {}. The default constructor doesn't do anything relevant in our case. That is a typical disadvantage of temporary variables. It slows down the execution time, and may slow down the execution time quite a lot in more complex situations. We would like to avoid the creation of temporary objects as much as possible. That's easy enough in this case, since we have another constructor that does a good job on initialising the two member variables:
Complex(double r, double i): m_re(r), m_im(i) {}.
We will find out how to use this efficient constructor in a minute. But first you'll need some background information.
Each time when you create an instance of a class (let's call them simply objects) that object will call its appropriate constructor. If no self defined constructor exists, it will call a default constructor. A default constructor is generated by your C++ environment. This is actually what happens if you create a temporary object:
1)A call to its appropriate constructor is made.
2)A copy constructor (later much more on that) copies your temporay object to another place.
3)When the temporary object goes out of scope, its destructor will be called.
Imagine you are initialising a huge array with temporary objects. Then for each instance (!), the three steps as described above will happen! Sometimes we don't have much options, but now we do.
I'll teach you a well known trick, called “return value optimisation”. Before I do, I need to tell you a little secret about constructors.
Return Value Optimisation
Did you know that when you call a constructor, that constructor will create a temporary object for you? You won't see it, since that happens internally. But it happens alright. We are going to exploit that feature. That is all you need to know right now. A complete working demonstration will follow very soon. That will make everything clear.
So let's wrap up what we've got:
1)We have a suitable constructor to initialise our member variables.
Code:
Complex operator+(const Complex& rhs) const
{
Complex temp(m_re + rhs.m_re, m_im + rhs.m_im);
return temp;
}
2)Now we get rid of our temporary, and make use of the fact that this constructor created one internally already.
Code:
Complex operator+(const Complex& rhs ) const
{
return Complex(m_re + rhs.m_re, m_im + rhs.m_im);
}
Do you see what is happening here? We are returning the internally created temporary!
Same result in the end, but we gain efficiency. And now it is time for the final code. Copy and paste the code below and experiment with it.
Code:
#include <iostream>
using namespace std;
class Complex
{
public:
Complex operator+(const Complex& rhs) const
{
return Complex(m_re + rhs.m_re, m_im + rhs.m_im);
}
void print() const
{
cout<<"("<<m_re<<" , "<<m_im<<")";
}
public:
Complex(double r, double i): m_re(r), m_im(i) {}
Complex() : m_re(0), m_im(0) {}
private:
double m_re, m_im;
};
int main()
{
Complex A(12.347, 15), B(-2, 5.45), Result;
Result=A+B;
Result.print();
cout<<endl;
return 0;
}
Copy, paste and run it. And experiment with it. From now on it will get slightly harder. Well, not really harder, as long as you follow the rules of engagement, which will be discussed when it is time to do so. And the overloaded
+ will be overloaded in a different way later, to avoid problems that are currently not relevant. If you understand what we did until now, then you're definitely on your way. Also be aware that our "constructor-efficiency" is going to be used in our custom String class. Our "constructor trick" is our first rule of engagement!
Be aware to use the Return By Value Optimisation.
There is another rule of engagement.
Do you agree that our overloaded
operator+ does the
natural thing, within the scope of our class? It does exactly the thing we'd expect it to do with complex numbers. So our second rule of engagement is:
Let overloaded operators do the natural thing.
I've recently seen some code, where the programmer overloaded an operator+, but it didn't add anything. It multiplied...
It distracted me for a minute alright. Truly:
Coding
is communicating!
I asked you to play a bit with our current code. To get familiar with the concepts you've just learned. The next paragraph will cover hot steaming new stuff. So feel free to take a break now. But save the final code listing. You still need it.