Class notes from Feb. 17 2003 INITIALIZING DATA MEMBERS IN AN OBJECT There is a special way that you can initialize your data members when an object is created. This is known as a member initializer list. It goes in any constructor, after the header and before the function itself. After the header, put a ":", and a comma-separated list of each variable and how you want to initialize it. The web site for today has an example program showing this. COPY CONSTRUCTOR, ASSIGNMENT OPERATOR, DESTRUCTOR There are 3 functions in a class that you should always provide. This is very important when start doing dynamically allocated memory, so let's get into the habit of providing these functions. 1. The copy constructor The copy constructor is called under 3 different circumstances. a. You create an object by making a copy of an existing object. e.g. Card c1(c2); // The Card copy constructor for c1 is called b. You pass an object by value to a function. A copy is automatically made and this copy is made by the copy constructor. c. You return an object from a function. Again, a copy is automatically made. The prototype of the copy constructor goes in the class declaration and is always of the same form. Here's an example, the copy constructor for a Card class: class Card { public: ... Card(const Card &orig); // copy constructor - copy contents of orig ... } The implementation of the copy constructor has one job: copy the data from the original into this object. Here's an example: Card::Card(const Card &orig) { myValue = orig.myValue; // copy each data element from orig myFace = orig.myFace; // into this object } 2. The destructor. The destructor is called when a statically declared object goes out of scope (typically at the end of the function it's declared in), or when a dynamically allocated object is deleted. The job of the destructor is to clean up. This might include closing files or releasing (deleting) any memory that the object itself allocated. The prototype for the destructor goes in the header, and always has the same form. Here's an example. class Card { public: ... virtual ~Card(); ... } The name of the function is ~Card. The word "virtual" is not a return type - it is needed when you use an object-oriented programming technique called inheritance. At this point, if you leave out the word "virtual" it will not cause you problems, but you might as well get in the habit of using it now. Here's an example of implementing a destructor: Card::~Card() { // This class does not dynamically allocate memory. There is no // clean-up to do. } 3. The assignment operator (operator=) The operator= function is called whenever you use the assignment operator. For example, if you say Card c1, c2(Two, Clubs); c1 = c2; // the operator= function is called. c3 = c2 = c1; // These operators may be cascaded - they are evaluated // from right to left The prototype for the operator= function goes in the class declaration. It always has the same form. Here's an example. class Card { public: ... const Card &operator=(const Card&rhs); ... } Note that the parameter is a const Card &, and refers to the right hand side of the assignment. Note that the return value is also a const Card &. The assignment operator has a job that's similar to the copy constructor, but there are a couple of things to worry about. First, you must check that the two sides of the assignment do not represent the same object. Then copy the data, just as in the copy constructor. Lastly, to allow for cascaded operators, we must return a const reference to this object itself. Here's an example of an implementation of operator=: const Card & Card::operator=(const Card&rhs) { if (&rhs != this) { // only copy if the two objects aren't the same // "this" is a pointer to this object itself myValue = rhs.myValue; // copy all the data myFace = rhs.myFace; } return *this; // return a reference to this object itself // the last line allows for cascaded operators }