The program src/const1.cpp defines a class called My_Class, which has two constructors -- one with parameters and one without. Both constructors print out the fact that they have been called. Then both the main() and proc() procedures declare two instances of My_Class. This means that the constructors will be called as soon as the procedures are called. They both print some statements, and main() also creates some My_Class instances with new:
#include <iostream> #include <cstdio> using namespace std; /* I'm declaring this class to have two constructors -- one without parameters, and one with parameters. */ class My_Class { public: My_Class(); My_Class(int a, const string &s); }; /* The constructor without parameters prints the string, "Constructor called without parameters. */ My_Class::My_Class() { cout << "Constructor called without parameters" << endl; } /* The second constructor prints out its parameters. */ My_Class::My_Class(int a, const string &s) { cout << "Constructor called with parameters " << a << " and " << s << endl; } /* The procedure proc() simply declares two instances of My_Class, prints a line and exits. */ | void proc() { My_Class c3; My_Class c4(30, "Baby-Daisy"); cout << endl << "proc begins and ends here." << endl; } /* Main has two My_Class's declared as local variables, and two pointers, where the My_Class instances are created with new. */ int main() { My_Class c1; My_Class c2(5, "Fred"); My_Class *cp1, *cp2; cout << endl << "Main is starting" << endl; cout << "calling: cp1 = new My_Class;" << endl; cp1 = new My_Class; cout << "cp2 = new My_Class(10, \"Luther\");" << endl; cp2 = new My_Class(10, "Luther"); cout << "Calling proc():" << endl << endl; proc(); return 0; } |
When we run it, it runs to expectation, but you may find it confusing -- I've annotated the output:
UNIX> bin/const1 Constructor called without parameters # These are when main() starts running. Constructor called with parameters 5 and Fred Main is starting # Here's the main body of main(). calling: cp1 = new My_Class; Constructor called without parameters cp2 = new My_Class(10, "Luther"); Constructor called with parameters 10 and Luther Calling proc(): Constructor called without parameters # These are when proc() starts running. Constructor called with parameters 30 and Baby-Daisy proc begins and ends here. # And here's the body of proc(). UNIX>To handle errors in a constructor, you need to throw an exception. To demonstrate, take a look at src/const2_exception.cpp:
#include <iostream> #include <cstdio> using namespace std; /* I've gotten rid of the first constructor, and I'm making the second constructor throw an exception when its parameter a is zero. */ class My_Class { public: My_Class(int a); }; My_Class::My_Class(int a) { printf("Constructor. A = %d\n", a); if (a == 0) throw ((string) "Throwing exception"); } /* Proc declares two My_Classes -- one with a value of 1 and a second one with a value of zero. It does a try/catch, but neither will get called because c2's declaration throws an exception. */ | void proc() { My_Class c1(1); My_Class c2(0); try { cout << "In proc -- try\n"; } catch (const string s) { cout << "In proc -- catch " << s << endl; } } /* Main calls proc(), and catches exceptions. */ int main() { try { proc(); cout << "This line won't print" << endl; } catch (const string s) { cout << "Caught " << s << endl; } return 0; } |
When we call this, the flow of control works as follows:
UNIX> bin/const2_exception Constructor. A = 1 Constructor. A = 0 Caught Throwing exception UNIX>This may seem confusing to you. Make sure you can trace through code like this.
class My_Class { public: My_Class(); My_Class(int a, const string &s); ~My_Class(); // This is the destructor }; |
My_Class::~My_Class() { cout << "Destructor called." << endl; } |
/* Here's the new code in main() */ cout << endl << "Calling delete on cp1" << endl ; delete cp1; cout << endl << "Main is returning" << endl ; return 0; } |
UNIX> bin/const3_destructor Constructor called without parameters Constructor called with parameters 5 and Fred Main is starting calling: cp1 = new My_Class; Constructor called without parameters cp2 = new My_Class(10, "Luther"); Constructor called with parameters 10 and Luther Calling proc(): Constructor called without parameters Constructor called with parameters 30 and Baby-Daisy proc begins and ends here. Destructor called. # These two are for cp3 and cp4 in proc() Destructor called. Calling delete on cp1 Destructor called. # This is from the delete call Main is returning Destructor called. # These two are for cp1 and cp2 in main() Destructor called. UNIX>You'll note that no destructor was called for cp2. That's because we never called delete on it. So we have a few important thing about destructors:
#include <iostream> #include <cstdio> using namespace std; /* My_Class has a string, which it sets in the constructor. It also has a destructor that prints the string. This program will make a few nested procedure calls, each of which has four My_Class local variables. In the deepest nesting, I throw an exception, which I catch in main(). I do this to show how all of the destructors get called as the exception travels back to main(). */ class My_Class { public: My_Class(const string &s); ~My_Class(); string my_string; }; /* The constructor sets my_string, and the destructor prints it. */ My_Class::My_Class(const string &s) { my_string = s; } My_Class::~My_Class() { cout << "Destructor called. my_string = " << my_string << endl; } /* p1, p2, p3 declare four My_Class's. p3 throws an exception. */ | void p3() { My_Class c1("p3_c1"), c2("p3_c2"); My_Class c3("p3_c3"), c4("p3_c4"); throw(0); } void p2() { My_Class c1("p2_c1"), c2("p2_c2"); My_Class c3("p2_c3"), c4("p2_c4"); p3(); } void p1() { My_Class c1("p1_c1"), c2("p1_c2"); My_Class c3("p1_c3"), c4("p1_c4"); p2(); } /* Finally, main() allocates one My_Class, then calls p1(), catching the exception. */ int main() { My_Class c1("main_c1"); try { p1(); } catch (int i) { cout << "Caught " << i << endl; } return 0; } |
When we call this, you'll note:
UNIX> bin/const4_dest_except Destructor called. my_string = p3_c4 Destructor called. my_string = p3_c3 Destructor called. my_string = p3_c2 Destructor called. my_string = p3_c1 Destructor called. my_string = p2_c4 Destructor called. my_string = p2_c3 Destructor called. my_string = p2_c2 Destructor called. my_string = p2_c1 Destructor called. my_string = p1_c4 Destructor called. my_string = p1_c3 Destructor called. my_string = p1_c2 Destructor called. my_string = p1_c1 Caught 0 Destructor called. my_string = main_c1 UNIX>Starting in C++ 11, you can't catch an exception thrown in a destructor. It has to do with the program that you just saw -- if you need to call those destructors while catching an exception, then you shouldn't be throwing a new exception. The impact: Just don't throw an exception from a destructor.
If you want to read typical arrogant and abusive blather on the topic, just Google "why can't i throw an exception in a destructor in C++" and read the prose on Stack Overflow...
/* We resize a vector of My_Class elements and exit, just to see what the constructors and destructors do */ class My_Class { public: My_Class(); ~My_Class(); }; | My_Class::My_Class() { cout << "Constructor called." << endl; } My_Class::~My_Class() { cout << "Destructor called." << endl; } | int main() { vector <My_Class> v; v.resize(5); return 0; } |
Here we go -- five constructor calls, and five destructor calls:
UNIX> bin/const5_vector1 Constructor called. Constructor called. Constructor called. Constructor called. Constructor called. Destructor called. Destructor called. Destructor called. Destructor called. Destructor called. UNIX>Now, suppose we resize by giving a default value. This is done in src/const6_vector2.cpp:
/* Now we resize with a default value. */ class My_Class { public: My_Class(); ~My_Class(); }; | My_Class::My_Class() { cout << "Constructor called." << endl; } My_Class::~My_Class() { cout << "Destructor called." << endl; } | int main() { My_Class c; vector <My_Class> v; // We resize with a default v.resize(5, c); return 0; } |
When we run it, we only get one constructor call, and six destructor calls:
UNIX> bin/const6_vector2 Constructor called. Destructor called. Destructor called. Destructor called. Destructor called. Destructor called. Destructor called. UNIX>What gives? Well.....
/* This program explores the copy constructor. */ class My_Class { public: My_Class(); My_Class(const My_Class &mc); ~My_Class(); }; /* This is the copy constructor, which is called when a vector is resized with a default, or when you call a procedure with an instance of the class as a non-reference parameter. */ My_Class::My_Class(const My_Class &mc) { (void) mc; cout << "Copy Constructor called." << endl; } | My_Class::My_Class() { cout << "Constructor called." << endl; } My_Class::~My_Class() { cout << "Destructor called." << endl; } void proc1(My_Class c) { (void) c; } void proc2(My_Class &c) { (void) c; } | void proc3(const My_Class &c) { (void) c; } int main() { My_Class c; vector <My_Class> v; v.resize(5, c); cout << "vector resized" << endl; proc1(c); proc2(c); proc3(c); cout << "returning" << endl; return 0; } |
You'll note that in addition to the copy constructor, I have three procedures:
UNIX> bin/const7_copy Constructor called. # This is for c Copy Constructor called. # These five are when we resize the vector Copy Constructor called. Copy Constructor called. Copy Constructor called. Copy Constructor called. vector resized Copy Constructor called. # This is when proc1() is called - we copy the argument Destructor called. # This is when proc1() returns returning # Main() prints this right before it returns Destructor called. # There are six of these - 5 for the vector, and one for c Destructor called. Destructor called. Destructor called. Destructor called. Destructor called. UNIX>If you don't define a copy constructor, a default is defined for you, that simply copies each member variable of the class. Typically, that's what you want, so you don't define a copy constructor. Think about that in our bitmatrix lab -- the only member variable is a vector M. The copy constructor copies that vector, so it does exctly what you want. If your class does some weird stuff, (typically involving pointers or open files), you'll want to rewrite the copy constructor.
To implement the Print() method, we simply call Print_Person() on this, which is a pointer to the class. Take a look (in src/this.cpp).
/* A program to demonstrate "this" */ #include <iostream> #include <cstdio> using namespace std; class Person { public: string Name; int Age; void Print(); }; /* Print_Person() takes a pointer to a person, and prints out the person with some formatting. */ void Print_Person(Person *p) { printf("Person: %s - Age = %d\n", p->Name.c_str(), p->Age); } /* The Print() method uses Print_Person() -- this is a pointer to the instance which is calling Print(). */ void Person::Print() { Print_Person(this); } /* In main(), we read in pairs of (name,age), and then use the Print() method of the Person class to print the person. */ int main() { Person p; while (cin >> p.Name >> p.Age) p.Print(); return 0; } |
We run it by passing two pairs of names and ages to standard input -- as you can see, the Print() method calls Print_Person(), using this as a pointer to the Person calling Print():
UNIX> echo Fred 5 Luther 55 | bin/this Person: Fred - Age = 5 Person: Luther - Age = 55 UNIX>
Much like the copy constructor, each class has a default assignment operator. This is the equals sign. If you say:
a = b; |
Where a and b are instances of a class, the default action is for a to receive a copy of each of b's member variables. More often than not, this is what you want. However, there are times when it's not, and when that's the case, you can redefine it. Let me show you an example, which is contrived, but it demonstrates how you can redefine the operator. It's in src/overload.cpp:
/* A program to show an assignment overload */ #include <iostream> #include <cstdio> using namespace std; class Crazy_Class { public: int value; Crazy_Class& operator= (const Crazy_Class &cc); }; /* The assignment overload copies value+1 from the original class. */ Crazy_Class& Crazy_Class::operator= (const Crazy_Class &cc) { value = cc.value+1; return *this; } int main() { Crazy_Class c1, c2; c1.value = 5; c2 = c1; // This will call the overload, and set c2.value to 6. cout << c2.value << endl; // This prints 6. } |
The assignment overload copies value+1 to value. So, when you say "c2 = c1", it calls the assignment overload, and sets c2.value to (c1.value+1)=6. Again, this example is contrived, but there are times, again when you're using pointers or open files in a class, that you'll want to redefine the assignment overload.
UNIX> bin/overload 6 UNIX>
I'm going to give you some opinions and recommendations now, with respect to C++ and object oriented programming. You don't have to heed these, but you should take serious consideration of them, and think about why I make them. I'll tell you that I am strict with respect to these, and I expect the students who work for me to be strict as well. Here we go:
In both C and C++, you can declare variables pretty much anywhere. I declare variables at the top of my procedures, before any code. That way, you know where to look for them, and you know that they are not undergoing any changes until the first line of real code. It makes the code readable, and easy to reason about. I am sure I'm in the minority here, but again, I have a lot of experience. When you read my code, you know where to find stuff.
It's much more convenient for the programmer to violate this. Let's take stringstreams for example. When I read a line of text and convert it to a vector of strings, I do the following:
int main() { istringstream ss; vector <string> sv; string line, word; while (getline(cin, line)) { ss.clear(); sv.clear(); ss.str(line); while (ss >> word) sv.push_back(word()); ... |
It would be more convenient for me to simply declare ss and sv inside the loop:
int main() { string line, word; while (getline(cin, line)) { istringstream ss; vector <string> sv; ss.str(line); while (ss >> word) sv.push_back(word); ... |
Now I don't have to call clear(). I can tell you I intensely dislike code like this -- again, I want declarations before code, and I don't want those declarations to be executing things. Then you know where to find stuff, and you have reasonable assurances what the state of the stuff is when the first line of code is executed.
Let me give you a really extreme example, in src/code_in_declarations.cpp:
#include <iostream> using namespace std; /* Here's a program where the constructor has side effects on its parameter. By the time you're done declaring parameters, the value of i is 105. */ class My_Class { public: My_Class(int &i); }; My_Class::My_Class(int &i) { i += 50; } int main() { int i = 5; My_Class c1(i), c2(i); cout << i << endl; return 0; } |
As mentioned in the comments, by the time you print i, the two constructors have changed its value to 105. You may say that this is an extreme example, but once you start intermixing variable declarations, code, and complex constructor operations, anything goes, and you can get code like this.
class My_Class { public: My_Class() : i(5) {}; protected: int i; }; |
This can be derived from the first recommendation. If you declare an instance of a class as a local variable with parameters, you are executing code. Often, you can't even declare it until you have executed some code that lets you set its parameters. Here's an example -- recall from the Bitmatrix lab, that we defined a hash table of bitmatrices:
class BM_Hash { public: BM_Hash(int size); ... |
Now, suppose you want to create a hash table, and read the table's size from standard input. When you use a pointer, the code is nice and clean:
void main() { int table_size; BM_Hash *hash_table; cin << table_size; hash_table = new BM_Hash(table_size); .... |
If you don't want to use a pointer (and I know that y'all students don't want to use pointers), you are forced to declare hash_table in the middle of your code:
void main() { int table_size; cin << table_size; BM_Hash hash_table(table_size); .... |
Convenient? Yes. Easy to read and reason about? No. I don't do it.
This may be inconvenient, because then you have to account for an "empty" instance of the class. Let's use the hash table above to exemplify. Its constructor takes a parameter - the table size. For that reason, you never have an empty hash table. On the flip side, if you are going to follow my rules, you'll always use a pointer to it, and you'll always have to call new to create it. Suppose instead that you don't have a parameter to the constructor. Then, you don't even need a constructor -- when you create an instance, the table will be empty. You'll have to add a method, like Set_Size(), to set the hash table's size, and then in your other hash table methods, you'll have to take into account that the hash table may be empty, and return the appropriate errors.
So it's a trade-off, certainly in code complexity, but you get the ability to declare the class very easily as a local variable. You also get the ability to resize a vector of the class without specifying a default value. There will be additional advantages when we get around to maps as well.
So consider that, when you are designing your classes.
When someone else uses your class, they will very reasonably expect to be able to make copies of class instances, either through a procedure call, or through the assignment operator. You should do the following:
class My_Class { public: My_Class(const MyClass &c) = delete; My_Class& operator= (const My_Class &cc) = delete; .... } |
Now, if a user attempts an operation that would call the copy constructor or assignment overlead, the compiler will emit an error. This is much better than ignoring the copy constructor, and then having it not work when someone calls it.
Since someone is bound to ask me about this, you can define a "move" constructor as well in the later versions of C++ (11 and beyond). The point of this is to optimize performance when you return a large data structure from a procedure/method. Move constructors are beyond the scope of this class, sorry.
I'm going to give two example programs here. The first is in src/vector_pointer.cpp
/* A program to demonstrate why it is dangerous to hold a pointer to an element of a vector, because it may get moved. */ #include <iostream> #include <cstdio> #include <string> #include <vector> using namespace std; int main() { vector <string> v; string *sp; string line; while (getline(cin, line)) { v.push_back(line); if (v.size() == 1) sp = &(v[0]); // I set sp to point to the first string in v. cout << v.size() << " " << *sp << endl; // I then print v's size, and *sp. } return 0; } |
This program reads lines from standard input, and pushes them onto v. It sets sp to point to the first string in v, right after it is put onto v. At the end of every iteration, it prints v's size and *sp. Let's see it in action:
UNIX> cat files/input.txt Kaylee Hex Abigail Piezoelectric MD Charlie Waive Gabriel Oshkosh Lucas Some Maya Extravaganza Isaiah Stiletto Cooper Encumber Kayla Littleneck Lillian Coroutine UNIX> cat files/input.txt | bin/vector_pointer 1 Kaylee Hex 2 Kaylee Hex Segmentation fault UNIX>That's not good. What happened? When the program called v.push_back("Charlie Waive"), the vector needed more memory, so it resized itself to a different memory location, and then copied the strings that were in the old location to the new location. And then they deleted the old location. Frankly, this could have happend after v.push_back("Abigail Piezoelectric MD"), and the memory at sp didn't get reused until v.push_back("Charlie Waive"). Regardless, after v.push_back("Charlie Waive"), the memory pointed to by sp has been changed so that it is no longer a string, and we are alerted to it by the segmentation violation. Keep that in mind.
Here's a second example, that got me when I was first using exceptions. If you throw a string literal, like:
throw("Error"); |
The type of the argument is (const char *), which means that to catch it, you need to do:
catch (const char *s) { |
Suppose that you've already written the code to throw a string literal, and then later in the same constructor, you want to construct a custom error message as a string and throw the string. Unfortunately, you can't catch it with the statement above -- it has to catch a string. So, to avoid writing a second catch() statement and possiblyl duplicate code, what I did was throw the c_str() method of the string. Let me demonstrate with some code, in src/exception_char_star.cpp. I'll start with the class definition, and the constructor, which thows a string literal when a equals zero, and uses a stringstream to construct an error string when a < 0. It then converts the stringstream to a string and uses the c_str() method so that it is throwing a (const char *):
class My_Class { public: My_Class(int a); }; My_Class::My_Class(int a) { ostringstream oss; if (a == 0) throw("Bad constructor for My_Class(). A shouldn't equal zero."); if (a < 0) { oss << "Bad constructor for My_Class(). A shouldn't equal " << a ; throw(oss.str().c_str()); } } |
In the main() I call new, first when a is zero, and then again when a is negative.
/* Here, I catch (const char *)'s rather than strings. */ int main() { My_Class *cp; try { // This one works fine, because the string literal is cp = new My_Class(0); // really a global variable. } catch (const char *s) { printf("%s\n", s); } try { // This one doesn't work, because the oss's destructor is called, cp = new My_Class(-5); // and the memory pointed to by s has been freed up (and reused) } catch (const char *s) { printf("%s\n", s); } return 0; } |
The first throw() gets caught fine, because that string literal is a global variable. The second throw gets caught, but because the constructor has exited, the stringstream has been deallocated (actually, technically, the string created by the str() method has been deallocated). That means that the pointer that was returned by c_str() is no longer valid.
When we run it, the second catch() prints an empty string. To be honest with you, anything could happen there. It just happens to print an empty string on my Macintosh.
UNIX> bin/exception_char_star Bad constructor for My_Class(). A shouldn't equal zero. UNIX>The solution to this is to always throw strings. If you have a string literal, put (string) in front of it, so that the throw() statement throws a string:
throw((string) "Error"); |