The material contained on this page will not be on the final exam.
During the last lecture, I discussed some C++ topics that were not present in the textbook. Some students asked me to post the notes that I presented during that lecture. Well, here they are. They are not meant to be complete, since they are notes, meaning that I used them to illustrate each topic. However, they can be used to refresh your memory of Wednesday's class.
Throughout the term, several students have asked questions relating to these topics. Some of these topics require an entire lecture to fully understand. However, I'm providing you with a peek at some of the more common features of C++ that some of you may want to pursue after this course is over. None of these topics are discussed in the textbook, so you will have to consult other books for additional information.
Also, keep in mind that the coverage here is only cursory; Some of these topics require an entire chapter of a book to adequately explain the details. Consider this page a very brief introduction to these other C++ concepts. The truth is out there...
The this Pointer
Given the specification and definition of an Integer class, what is wrong with the implementation of setValue?
The name of the parameter (value) conflicts with the private member value. We can solve this problem by explicitly qualifying the private data member:class Integer { public: Integer(void); void setValue(int value); int getValue(void); void Display(void) const; private: int value; }; Integer::setValue(int value) { value = value; } int Integer::getValue(void) { return value; } void Integer::Display(void) const { cout << value << endl; }
All member functions have a "hidden" parameter named this. It is a pointer to the object that invoked the method. For example, to the compiler, this is how the function is actually seen:Integer::setValue(int value) { this.value = value; }
There are times when you need to know which object has invoked a member function and the this pointer is a mechanism that allows you to access the object. In the simple example above, the this pointer is merely used to resolve ambiguity between a private data member and a function parameter. You'll find a need to access the this pointer when implementing operator overloading.Integer::setValue(Integer* const this, int value);
Inline Functions
Inline functions allow programmers to protect their private data and at the same time provide efficiency for clients of the code. We can modify the Integer class to include an implicit inline function:
Or, we can modify the implementation to include an explicit inline function:class Integer { public: Integer(void); void setValue(int value); int getValue(void) { return value }; void Display(void) const; private: int value; };
Note that this is only a hint to the compiler and that there are other restrictions and limitations when using inline functions. Also, not all functions may be inlined. Can you think of one that might not? (a recursive function)class Integer { public: Integer(void); void setValue(int value); int getValue(void); void Display(void) const; private: int value; }; inline int Integer::getValue(void) { return value; }
Exception Handling
The topic of exception handling is large and can seem complex at first. The simple examples below can prove to be quite useful. In fact, in order for my driver program for Lab #4 to run from start to finish without a core dump, it was necessary to use the techniques below.
This code is from the driver program used to test the StrList class implemented in Lab #4:void main(void) { int a = 5, b = 0, c; c = a / b; // crash and burn } void main(void) { int a = 5, b = 0, c; try { c = a / b; } catch(...) { cout << "Caught an exception" << endl; } } ------------------------------------------------------------- void boom(void) { char *p = NULL; cout << *p << endl; // Kaboom! } void main(void) { boom(); } void main(void) { try { boom(); } catch(...) { cout << "It went BOOM!" << endl; } }
Overloaded Operatorstry { cout << "getCount() returned " << list.getCount() << endl; } catch(...) { cout << "ABNORMAL TERMINATION getCount()" << endl; } try { cout << "isEmpty() returned " << list.isEmpty() << endl; } catch(...) { cout << "ABNORMAL TERMINATION isEmpty() on empty list" << endl; } try { cout << "isPresent(Ten) returned " << list.isPresent("Ten") << endl; } catch(...) { cout << "ABNORMAL TERMINATION isPresent() on empty list" << endl; } try { list.Display(); } catch(...) { cout << "ABNORMAL TERMINATION to display empty list" << endl; }
The following program uses the String class we developed during the course. The program may crash because the compiler performs a shallow copy between str1 and str2. But it's not the copy that causes the problem. When the destructor is called for str1, the data pointed to by the private data member string will be deleted. When the destructor for str2 is called, the system tries to delete the data pointed to by the private data member string , but that data has already been deleted because str1 and str2 both point to the same allocated memory. Depending on the compiler you use, your program may crash or it may not. In either case, it's a Bad Thing.
The fix to this is to make the assignment operator "smarter" so that it performs a deep copy instead. We do this by overloading the assignment operator. While we're at it, we will overload the '+' operator to concatenate two Strings. (This is not the entire specification of our String class.)void main(void) { String str1("Hello"); String str2; str2 = str1; }
We can test out our new operators with this code:class String { public: String(void); String(char* str); ~String(void); String(const String& otherString); void Set(char* str); void Set(const String& otherString); // Overloaded operators for = and + String operator=(String& otherString); String operator+(String& otherString); private: char* string; }; // This makes the assignment operator perform // a deep copy String String::operator=(String& otherString) { delete [] string; string = new char[strlen(otherString.string) + 1]; assert(string); strcpy(string, otherString.string); return *this; } // This allows us to concatenate two Strings String String::operator+(String& otherString) { // The length of the new string will be // length of string + length of otherString.string int len = strlen(string) + strlen(otherString.string); char* tempStr = new char[len + 1]; assert(tempStr); // Concatenate the strings together strcpy(tempStr, string); strcat(tempStr, otherString.string); // Create a new String String tempString(tempStr); // Delete temporary array delete [] tempStr; // Return the new String return tempString; }
The output should look like this:void main (void) { String str1("Hello"); String str2(", "); String str3("World"); String str4, str5; // Calls operator= to perform a deep copy str4 = str5 = str1; cout << "str4 is "; str4.Display(TRUE); cout << "str5 is "; str5.Display(TRUE); // Calls operator+ twice, then operator= str5 = str1 + str2 + str3; cout << "Now str5 is "; str5.Display(TRUE); }
str4 is Hello str5 is Hello Now str5 is Hello, World