Previous | Table of Contents | Next |
7.3.4.3. Assignment Operators
The next special member function to consider is the assignment operator. It is similar to a copy constructor, but instead of having the same name as the class itself, it is called operator=. By convention, the assignment operator should return a reference to its left-hand sidethat is, a reference to *this. So, for example, we might define an assignment operator for our Point class as
class Point { public: Point& operator=(const Point& p) { xval = p.xval; yval = p.yval; return *this; } // as before };
Note that we do not use constructor initializers in the assignment operator, because xval and yval are being assigned, not initialized, and because the assignment operator is not a constructor.
As with the copy constructor, assignment operators should have reference parameters. However, an assignment operator that takes a Point rather than a Point& will merely cause needless calls to the copy constructor, rather than a catastrophic failur
If a class does not have any assignment operator at all, the compiler will create one that assigns all the members of the class and returns a reference to the left-hand side.
7.3.4.4. Destructors
A destructor runs automatically whenever an object of its class is destroyed. Like constructors and assignment operators, destructors are distinguished by name: A destructors name is the name of its class, preceded by a tilde (~). As with constructors and assignment operators, the compiler will generate a destructor if we do not write one. However, destructors behave slightly differently in this regard from the other special member functions, because whatever we write in our destructor will take place in addition to the default action, which is to destroy the data members. This makes sense because there is no way to escape the requirement to destroy the members of an object as part of destroying the object itself.
Our Point class does not need a destructor, but if it did, it would look like this:
class Point { public: ~Point() { /* ... */ } // as before };
A member of a class object is an object in its own right, so that it is possible to form a pointer to it:
struct Thing { int n; double x, y; }; void f() { Thing t; double* dp = &t.x; }
Here, dp is caused to point to the x member of a particular Thing object.
It can be useful, however, to identify a member of a class without identifying a particular object of that class. We do that with a special type called a pointer to member. We might, for example, use &Thing::x to refer to the member x of class Thing. The type of &Thing::x is pointer to member of Thing of type double, and we can store such a value in a variable by writing
double Thing::*p;
This declaration says that p is a pointer to a member of class Thing that has type double; we can give p a value by writing
p = &Thing::x;
If t is a Thing object, we can then use the expression t.*p as another way of referring to t.x. Similarly, if tp is a pointer to Thing, tp->*p is another way to refer to tp->x.
We have finally learned enough C++ so that we can define a class called String whose objects represent variable-length character strings. In particular, we will make it possible to say
String s = Hello, world!; cout << s << endl;
and have the program greet the world accordingly.
We will first note that, because arrays have fixed length, there is no way to store a variable-length string directly in a class object. Instead, we will represent a string as a pointer to the initial character of a dynamically allocated array of characters. To determine the length of the string, we will adopt the same convention that string literals use: The last significant character of the string will be followed by a null character \0.
As part of our implementation, we will use two simple functions from the standard library. We have already seen one of them: strlen takes a pointer to the initial character of a null-terminated array and returns the number of characters, not counting the null. The other library function that we will use is strcpy. It copies the null-terminated string given by its second argumentincluding the null characterinto memory starting at the location given by its first argument. When it is done, it returns its first argument:
char* strcpy(char* p, const char* q) { char* r = p; while ((*p++ = *q++) != \0) ; return r; }
Here, all the work is done inside the loop: We copy the character at which q points into the character at which p points, remember the character, increment p and q, and test whether the remembered character is zero. If so, then we have just copied the null terminator and were done. Otherwise, were done with this trip through the loop, and its time to begin the next iteration.
We know how to move characters around; we know how we are going to store the characters of a String; what more do we need? When we design C++ classes, we will often encounter a close relationship between the copy constructor, destructor, and assignment operator. This relationship comes about because an assignment operator gets rid of the old value of the object and then gives it a new one, and those two operations are exactly what the destructor and copy constructor do. Newcomers to C++ therefore often ask if it is possible to call a constructor directly.
Previous | Table of Contents | Next |