Code Newbie
News     Forums     Search     Members     Sign Up    

My Code Newbie
Username

Password

Articles/Snippets
ASP Classic
ASP.NET
C
C#
C++
HTML / CSS
Java
Javascript
Linux / BSD
Perl
PHP
Python
Ruby
SQL
VB 6
VB.NET

C.N. Friends
  Planet Rome

Link to Us!
Code Newbie
  Code Newbie
    forums

Go Back   Code Forums > Application and Web Development > Standard C, C++

Reply
 
LinkBack Thread Tools Display Modes
Old 01-28-2005, 08:57 PM   #16 (permalink)
Valmont
[code][/code] enforcer
 
Valmont's Avatar
 
Join Date: Mar 2003
Location: Netherlands
Posts: 1,544
Valmont is on a distinguished road
fp_unit:
We return by reference to prevent the creation of temporaries. Temporaries get lost once we get out of the function or out of scope. See this page for details on it. This is the answer to the return-by-reference question on operator=().

Page 3:

*** Chapter One ***

Returning by reference. A recap.

There is no way one can decently understand and implement operator overloading without understanding the basics of a function that returns a reference. Observe the little code below. It will be followed by a short remark only. Refer to your study book if this topic is completely new to you.
- Step(1) int& SomeFunction enables SomeFunction to be used as a lefthandside value. Observe the placement of the ampersand (&)!
Code:
#include <iostream>
 
using namespace std;
int& SomeFunction(int& a, int& b, int& c)
{
  // Step(2) Variable "c" can change the variable it points to.
  return c;
}

int main()
{
  int MyInt_1 =7, MyInt_2=8, MyInt_3=9;
  
  /* Step(3)
  * The assignment below basically means: MyInt3 = 230;
  * Why? Because "c" is returned by SomeFunction, AND 
  * MyInt_3 is passed to "int& c", AND
  * there is an ampersand (&) in "int& SomeFunction". 
  */
  
  //Somefunction is lhs because of "int&" as return type!  
  SomeFunction(MyInt_1, MyInt_2, MyInt_3)=230;
  cout<<MyInt_1<<endl;  
  cout<<MyInt_2<<endl;  
  cout<<MyInt_3<<endl;
  
  return 0;
}
The output for MyInt_3 is 230! Not 9.
So how do you change MyInt_2? Why easy. Just return b instead of c in SomeFunction(int& a, int& b, int& c).
That's it. Now we move to our first example of operator overloading.
__________________
Valmont is offline   Reply With Quote
Old 01-28-2005, 09:01 PM   #17 (permalink)
Valmont
[code][/code] enforcer
 
Valmont's Avatar
 
Join Date: Mar 2003
Location: Netherlands
Posts: 1,544
Valmont is on a distinguished road
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.
__________________

Last edited by Valmont; 01-29-2005 at 12:15 PM.
Valmont is offline   Reply With Quote
Old 01-28-2005, 09:03 PM   #18 (permalink)
Valmont
[code][/code] enforcer
 
Valmont's Avatar
 
Join Date: Mar 2003
Location: Netherlands
Posts: 1,544
Valmont is on a distinguished road
fp_unit:
The following pages are related to the Rule of Three.

Page 11:

The Rule of Three

Have you ever heard of the Rule Of Three? If not, then you MUST read this mini-tutorial. This is how we are going to define it, using plain words as promised in the preface:



Well, since we are going to build such a class, we need to cover this. We are going to do that thoroughly! Luckily it is not that hard. Just follow me.

The problem is that we want to make code below work correctly:
Code:
void SomeFunction(const String& s)
{
  String B("Okidoky");
  B=s;
}


int main()
{
  String A = "Hello";
  String B(A);
  //Or String B = A;
  A = A;
  SomeFunction(A);
  String C = A;

  return 0;
}
To make this happen, we will need to develop our custom String class with care. Or better, we need to set up the basics of our class properly. Here is my global plan on how I am going to teach you to do just that:
  • Learn the difference between assignment and initialisation.
  • Learn why operator= is a must.
  • Learn why we must implement a copy constructor.
It will be a long walk if you're not familiar with this concept. But in the end it is worth it. Although this mini-tutorial-in-big-tutorial is off-topic, the whole purpose of this huge tutorial is to strengthen the student in his quest for building up a decent skill set. After finishing this mini-tutorial, you'll be strengthened alright...

Let's start.
__________________

Last edited by Valmont; 01-29-2005 at 12:10 PM.
Valmont is offline   Reply With Quote
Old 01-28-2005, 09:03 PM   #19 (permalink)
Valmont
[code][/code] enforcer
 
Valmont's Avatar
 
Join Date: Mar 2003
Location: Netherlands
Posts: 1,544
Valmont is on a distinguished road
Page 12:

Initialisation versus Assignment

This page is just making sure we know what we are talking about. There is a distinct difference between those two in C++.

Assignment

Assignment occurs in such a way, that an object is created but NOT assigned a value to it straight away:
Code:
String A;
A = "Hi there baby.";
This innocent piece of code is less innocent then we might think.

Initialisation
Initialisation occurs in such a way, that an object is created and assigned a value to it straight away. Actually, this definition is so utterly bad, I can't bare it. Luckily we are going to cover this in detail as well. First we need samples of initialisation:
Code:
String A("Hi there baby");
//or
String B = "Hi there baby";
As you can see, both are one-liners.

A more serious analysis

If we observe line 2 in the first code snippet, and line 3 in the second code snippet...
A = "Hi there baby."; vs String B = "Hi there baby.";
... then we can see that C++ makes a fundamental difference between various versions of =. Math doesn't have this distinction, but C++ has.

The assignment code needs to be worked out better in detail. This is what happens:
Code:
String A;
This line calls your default constructor, a self defined parameter less constructor, or a constructor that acts like a conversion operator. Only the latter is implemented by us later. Then:
Code:
A = "Hi there baby.";
This line does two things: 1) it calls the constructor once again 2) it calls the overloaded operator=.
So the whole snippet will do this in our String class later:
1)Call -> (conversion) constructor
2)Call -> (conversion) constructor
3)Call -> operator=
And finally:
4)Call -> destructor

The double call to the constructor seems inefficient. It is. More on that in a bit. Let's see what happens when we implement the second snippet: initialisation.
Code:
String A = "Hi there baby";
1)Calls -> (conversion) constructor
That's it. Not even the destructor or operator+ needed to be called. Wow. And this:
Code:
String A("Hi there baby");
Calls -> (conversion) constructor.
Same thing. That's it.

Passing objects around

We are going to analyse what happens when we execute these two snippets (that do the same in the end):
Code:
String A("Hi baby.");
String B(" Wanna dance?");
String C;
C = A + B;
Or
Code:
String A("Hi baby.");
String B(" Wanna dance?");
String C(A + B); //OR: String C = A + B;
Obviously the output in both cases is: Hi baby. Wanna dance?
We have enough knowledge to calculate what happens.

The first snippet:

Calls -> (conversion) ctor
Calls -> (conversion) ctor
Calls -> (conversion) ctor
Calls -> Copy ctor
Calls -> operator=()
Calls -> destructor


The second snippet:

Calls -> (conversion) ctor
Calls -> (conversion) ctor
Calls -> Copy ctor


Yet, once again, both snippets do the same thing. So the conclusion we can draw is that initialisation is more efficient. But we need both!

Basically here ends a recap on initialisation versus assignment. True, there is more to tell about it. But it won't be relevant to the main topic. For the sake of easy recognition: initialisations are the one-liners.

The next page will build the very foundations of our String class.

Quote:
I like the italics. Adjust!
__________________
Valmont is offline   Reply With Quote
Old 01-28-2005, 09:05 PM   #20 (permalink)
Valmont
[code][/code] enforcer
 
Valmont's Avatar
 
Join Date: Mar 2003
Location: Netherlands
Posts: 1,544
Valmont is on a distinguished road
fp_unit:
I will fix the images later. It is late now. However, you could read the images from the operator= page. The idea is the same!
Page 14:

The Copy Constructor

The very minimal expectations from the String class we could have, is that we are able to write code like this:

Code:
int main()
{
  String A("Ho ho ho. Santa here!");
  String B = ("Now, who's a good boy?");
  return 0;
}
Obviously we need to implement a suitable constructor. Let's do that right now:
Code:
#include <iostream>
#include <cstring>

using namespace std;

class String
{
public:
  String(const char* = "");
  ~String();
private:
  char* m_pChars;
};

//-------------------------------------------------------

//Acts like a conversion operator: 'c-style char' to 'String'
String::String(const char *s)
{
// Need to copy the given string. if s is zero, make a null string.

  if (s)
  {
    int len = strlen(s) + 1;
    m_pChars = new char[len];
    strcpy(m_pChars, s);
  }
  else
  {
    m_pChars = new char[1];
    *m_pChars = '\0';
  }
}

//---------------------------------------------------------

String::~String() { delete[] m_pChars; }

//----------------------------------------------------------

int main()
{
  String A("Ho ho ho. Santa here!");
  //Or, which is the same remember? The so called 'one liners'.
  String B = ("Now, who's a good boy?");
  return 0;
}
I've implemented the destructor as well. Notice the square brackets [] after the delete operator! We are deleting an array.

String::String(const char *s) is a constructor.
This one takes one argument. Therefore this constructor is automatically a (implicit) conversion operator from a "C-style string" to our String class. That is relevant because in code like A="Hello", "Hello" isn't a String object but a c-style string.
Also notice that our class uses dynamic allocated memory.

But what if we use the class as it is now, and implement this:
Code:
int main()
{
  String A("Ho ho ho. Santa here!");
  String B = A;
  //Or, which is the same remember? The so called 'one liners'.
  //String B(A);
  
  return 0;
}
RED ALERT. DEFCON 5.
This code is valid, make no mistake. The default present copy constructor will make this happen. Nevertheless something sneaky happens here: after the objects B and A go out of scope, the class will double delete the same data. It will call the destructor for object B, then it will call the destructor for object A. Right now that is a problem Let me show you the situation where A and B are initialised:


Now let me show you the situation where object B goes out of scope and therefore, calls its destructor:



So as soon the destructor for object A is called, we have a situation. The ISO C++ standard defines that the behaviour will be undefined. So your program might behave normally, or it might crash. I've tested it on two of my systems. One crashed indeed. But this one (the system I am working with right now) behaves as if no problem ever occurred. Usually, in (very )large programs an error will finally occur, but finding the cause of the problem will be one heck of a task.

Luckily we can solve this problem: Make a copy of the memory block. Copy it bit by bit, so both object A and Object B are responsible for their very own memory block. Below is this situation visualised:


Deleting Object B (or A) first causes no problems for the next deletion of the remaining object:


So to make this picture happen, we need to make a true copy of the members of the class. Well, there is only one member ( char* m_pChars ) so we won't have much work.
Here are the steps:

1) Override the default copy constructor:

Code:
String(const String&)
Or in general:
T (const T&)

2) Copy members:
Code:
String::String(const String& s)
{
  m_pChars = new char[strlen(s.m_pChars) + 1];
  strcpy(m_pChars, s.m_pChars);
}
So here the final code code listing with our own copy constructor:
Code:
#include <iostream>
#include <cstring>

using namespace std;

class String
{
public:
  String(const char* = "");
  ~String();
  String(const String& );
private:
  char* m_pChars;
};

//-------------------------------------------------------

//Acts like a conversion operator: 'c-style char' to 'String'
String::String(const char *s)
{
// Need to copy the given string. if s is zero, make a null string.

  if (s)
  {
    int len = strlen(s) + 1;
    m_pChars = new char[len];
    strcpy(m_pChars, s);
  }
  else
  {
    m_pChars = new char[1];
    *m_pChars = '\0';
  }
}

//---------------------------------------------------------

String::~String()
{
  delete[] m_pChars;
}

//----------------------------------------------------------

String::String(const String& s)
{
  m_pChars = new char[strlen(s.m_pChars) + 1];
  strcpy(m_pChars, s.m_pChars);
}


//----------------------------------------------------------

int main()
{
  String A("Ho ho ho. Santa here!");
  String B = A;
  //Or, which is the same remember? The so called 'one liners'.
  //String B(A);
  
  return 0;
}
You may want to single step through the destructor and observe that the second destructor holds valid data indeed.

In the implementation of our copy constructor we alocated m_pChars with new, but we never delete it in the body of this function.
QUESTION: Why not?


Why duhuuh. We need that dynamic allocated memory to avoid double deletion. Let the destructor take care of it.

I've asked this question because I've seen a few universities asking the same question on quizzes. Don't let them fool you. This whole tutorial is more about basic skill and confidence building than operator overloading actually! Often the answers are so simple as long as one stays level headed with C++.

We have now implemented TWO of items of the Rule Of Three:
1) The destructor.
2) The copy constructor.

One to go. Assignment operator overloading. That is obviously the next page. But once again, learn how to debug and debug the destructor before we implemented the copy ctor, and after we've implemented it.
__________________

Last edited by Valmont; 01-29-2005 at 12:09 PM.
Valmont is offline   Reply With Quote
Old 01-28-2005, 09:05 PM   #21 (permalink)
Valmont
[code][/code] enforcer
 
Valmont's Avatar
 
Join Date: Mar 2003
Location: Netherlands
Posts: 1,544
Valmont is on a distinguished road
Page 15.

The Assignment Operator

A we've seen an own implemented copy constructor is needed to prevent double deletion when one object is used to initialise the other object of the same type or class. We need an overloaded assignment operator as well to prevent double deletion. But there is a second reason why an overloaded assignment operator is needed: to enforce proper deletion.

Double Deletion

We will first cover double deletion.
This time we would like to prevent an attempt to double delete dynamically allocated memory if one object receives its data by assignment from another object of the same type or class. Below is a image to demonstrate once again the difference between assignment and initialisation.



As you can see, the assignment operator has two meanings here. In the first snippet, the copy constructor is invoked. In the second example the assignment operator is invoked. If you didn't provide an overloaded version then C++ will provide a default version. In the case where members allocate memory dynamically, we can't use the default assignment operator.

Time to demonstrate what is going under the hood again. You must use the String class as we defined it on the previous page. Thus with a copy constructor and an implemented destructor. Then let's find out what the function below does:
Code:
int main()
{
  String A("Hello");
  String B;
  B=A;

  return 0;
}
Again, this oh-so simple code causes us headaches if we rely on the default provided assignment operator. Look at the image below and observe what this snippet does:



As you can see, on destruction the behaviour is the same as our class without a copy constructor. Sadly enough a system with this implementation doesn't have to crash. The standard never defined a crash to start with. So we need to add a little code to demonstrate the double deletion. We need to modify the destructor slightly:
Code:
String::~String()
{
  cout<<"dtor called for: "<<this<<endl;
  cout<<"Output = "<<m_pChars<<endl;
  
  delete[] m_pChars;
}
Now lets run this program in a console (don't debug, just run the executable in the console or bash). This is what I see:



As you can see, the first call where object A is destroyed, the pointer m_pChars holds valid data, but the second call to the destructor, where object B is destroyed, the pointer does not point to the original data (memory). So deletion goes wrong. We need to prevent this. There is another issue.

Memory Leaks AND Double Deletion

Add now this to your code:
Code:
//----------------------------------------------------------

void change(String& s)
{
  String B("I am string B");
  B=s;
}

//----------------------------------------------------------

int main()
{
  String A("Hello");
  change(A);

  return 0;
}
That's right, leave our String class intact, and change() is a free function.
Run the application in console again and and observe the output. As you will see, the output is almost the same. Most likely this will resort in a different address-range. I will explain first with words what is going on, then I will present a big image to visualize the whole process in memory. This happens:


1) Object A is initialised.
2) Object A is passed to change() by reference.
3) Object s points to the same memory location as object A does.
4) Object B is initialised.
5) Object B points to the same memory location as Object A does.
6) Function exits, object B goes out of scope and calls its destructor. It deletes the same memory block as object A points to!
7) In main() object A goes out of scope as well, when the application stops. So the destructor for object A is called as well. But A's pointer points to nothing. BOOM.
8) The string "I am string B" remains in memory. It was never deleted!


A lot of things happen here. Time for an image where the 8 steps are visualised:



The image is not strictly accurate. But it shows clearly what the problems are. Time to solve them. We will explore both efficiency and correctness.
__________________
Valmont is offline   Reply With Quote
Old 01-28-2005, 09:08 PM   #22 (permalink)
Valmont
[code][/code] enforcer
 
Valmont's Avatar
 
Join Date: Mar 2003
Location: Netherlands
Posts: 1,544
Valmont is on a distinguished road
fp_unit:
This page isn't finished yet. Don't know when I have the time. But it gives you a starter.

Page 16:

The Assignment Operator: implementation

First the template, then the complete working code for you to try out. The skeleton for operator=:
Code:
String@1 String::operator=(const String& s) 
{
  if (this != &s)
  { 
    ...
  }
  return @2;
}
The mystery guests @1 and @2 aren't so mystery anymore because we covered them in detail earlier, where we implemented operator+= for the Complex class. Try to figure them out. Soon I will present them without an explanation.

QUESTTION: What was the purpose of the two mystery guests again?
Figure that out, and you'll know what code to type.


But something new is presented. A new rule of engagement. In case of overloading the assignment operator, we must check for self assignment.
Example 1: A = A
Example 2: A = B (if B is a reference to A!) A tricky one. This is called aliasing.

There are two good reasons to cope with possible self-assignment:
  • Efficiency.
  • Correctness (avoiding bugs again).

Efficiency

This is the lesser (and easier) part. Assignment basically means: change the state of the object. Assignment to self doesn't change the state at all, so to prevent executing more code than is needed, we have to be weary for self assignment.

Changing the state of an object usually means changing value of the properties for that object. In our case we are talking about m_pChars.

Correctness (part 1 of 2)

If assignment means that we are changing the state of an object (by changing the value of its properties in our case), then it means we need to assign a new value to the property for that object. We have to find out how this is done in the first place. So never mind correctness for now, and let us first implement a seemingly functional operator=. Once we have such an implementation, we can move back a little and find out about correctness in detail.

Moving On With The String Class: operator=

Assigning boils down to changing the value of m_pChars in our specific case. But m_pChars always contains data. The constructor String::String(const char* s = "") will make sure of that: If m_pChars does not contain user-defined data, then this variable holds an empty string. Note that the memory for this data is dynamically allocated. Therefore, assigning this variable with new data just like that, will keep the old data still present in memory. It becomes inaccessible and so that part of memory is lost for good. So we delete the old data first correctly. Remember, m_pChars is an array of C-style characters so use the proper delete keyword (in this incomplete code snippet):
Code:
String& String::operator=(const String& s) 
{
  {
    delete[] m_pChars;
  }
  return *this;
}
The mystery guests are finally filled in as well: a return by reference for chaining, and *this to return an object. The return by reference prevents the creation of temporaries and returning the object (*this) makes sure the next call is a valid call by an object. Reread overloading operator+= in the Complex class for the details if needed.

At the very beginning of this chapter we discussed how to deal with assigning, copying and setting up new string data for a C-style string array:
  • Calculate the needed size of the array..
  • Allocate the proper amount of new memory.
  • Use strcpy() when copying or assigning data.
So here are the next three lines:
Code:
String& String::operator=(const String& s) 
{
  {
    delete[] m_pChars;
    int len = strlen(s.m_pChars) + 1;
    m_pChars = new char[len];
    strcpy(m_pChars, s.m_pChars);
  }
  return *this;
}
This is a fully functional implementation. But it is buggy. I'll present the complete code (in case you lost track) for you to copy so we can quickly set up a test in main():
Code:
#include <iostream>
#include <cstring>

using namespace std;

class String
{

public:
  String& operator=(const String& );
public:
  ~String ();
  String(const char* s = "");
  String(const String& );
private:
  char *m_pChars;
};

//-------------------------------------------------------

//Acts like a conversion operator: 'C-style string' to 'String'
String::String(const char *s)
{
// Need to copy the given string. if s is zero, make a null string.

  if (s)
  {
    int len = strlen(s) + 1;
    m_pChars = new char[len];
    strcpy(m_pChars, s);
  }
  else
  {
    m_pChars = new char[1];
    *m_pChars = '\0';
  }
}

//---------------------------------------------------------

String::~String()
{
  delete[] m_pChars;
}

//----------------------------------------------------------

String::String(const String& s)
{
  m_pChars = new char[strlen(s.m_pChars) + 1];
  strcpy(m_pChars, s.m_pChars);
}

//----------------------------------------------------------

String& String::operator=(const String& s)
{
  {
    delete[] m_pChars; // first delete old
    int len = strlen(s.m_pChars) + 1; // +1 for '\0'
    m_pChars = new char[len];
    strcpy(m_pChars, s.m_pChars);
  }
  return *this;
}

//----------------------------------------------------------
And here is the main() so we can do a test:
Code:
int main()
{
  String A("Hello");
  String B;
  B=A;

  return 0;
}
The code is buggy and delivers an undefined result. We should test it by debugging. Insert a breakpoint at B=A; and single step through the code. You will see that the contents of B are cleared first (we free the memory nicely), then the contents of A are copied into B. Nothing weird or surprising here. It seems to work correct.
__________________
Valmont is offline   Reply With Quote
Old 01-28-2005, 09:08 PM   #23 (permalink)
Valmont
[code][/code] enforcer
 
Valmont's Avatar
 
Join Date: Mar 2003
Location: Netherlands
Posts: 1,544
Valmont is on a distinguished road
// --- done --- //
__________________
Valmont is offline   Reply With Quote
Reply

Bookmarks

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are On


Similar Threads
Thread Thread Starter Forum Replies Last Post
Any Ideas kickerman97 Java 2 12-06-2004 07:36 AM
Program ideas? Vladk1000 Standard C, C++ 9 06-10-2004 12:03 AM
Relative paths -- I need help and I'm out of ideas. Mr.Anderson PHP 2 09-22-2003 09:46 AM
Poll Ideas Ilya020 Feedback 2 03-17-2003 09:14 AM


All times are GMT -8. The time now is 06:04 AM.


Powered by vBulletin® Version 3.7.0
Copyright ©2000 - 2008, Jelsoft Enterprises Ltd.
Search Engine Optimization by vBSEO 3.0.0 RC8





Copyright © 2000-2008, Milano Interactive
Web Hosting provided by Portal 360 Web Hosting