C++ Constructors, Destructors and Default Methods Bolo Background Every C++ class has a minimum of 4 methods. They exist whether or not you create them. The methods are: +---------------------------------------------------------+ |What Name | +---------------------------------------------------------+ |Default Constructor A::A() | |Copy Constructor A::A(const A &) | |Assignment [const] A &A::operator=(const A &) | |Destructor A::~A() | +---------------------------------------------------------+ The default copy constructor and assignment construc- tors are to do a bit-wise copy of any non-class members of a class. Whitespace (padding) between class members is not copied. If a class contains any other classes, then the copy constructor or assignment constructor for that class is used instead of the bit-wise copy. Constructor and Assignment Necessities If a class contains a pointer, you must define your own versions of them because the class is just an accident wait- ing to happen. The first time a default copy constructor or assignment operator goes off, the ownership of pointed-to objects becomes questionable. If a class SHOULD NOT BE COPIED, then provide unimplimented, private copy construc- tors and assignment operators. At the language level people won't be able to use those methods. At the link level, any- thing that would generate an implicit call to those methods won't be able to find them. Note that this does not mean that any class that uses a class containing a pointer needs to define copy constructors or assignment operators. Because the language implicitly generates calls to copy constructors and assignment opera- tors for any class members it will just work. If you aren't going to provide or disable the 4 "always" methods for a class, don't define empty ones. It doesn't do you, me, or the compiler any good, and just con- fuses things if you try to use a tool that identifies classes missing copy constructors and assignment operators. Note there is one place you may want to have empty construc- tors. That is to expose a default constructor through inheritance. For example, an intermediate class that imple- ments a virtual function but that is not used itself. For example you make a shim class B that inherits from class A. You may need to copy all the A constructors to B to keep them exposed so that a B can act like an A. Remember that if you have any non-class member vari- ables that you need to have at least a default constructor to initialize those variables. A::A(const A &) {} IS NOT A COPY CONSTRUCTOR. It does nothing at all. Other Things C++ classes have colon, ":" initializers. These should be used instead of assignments in the body of the construc- tor methods. Why? Well, the compiler can help you out to find member variables that are not initialized, or that might be initialized in an unexpected order. It is the only way to initialize const members. By using the proper con- structors for the member variable classes in the first place, you reduce execution overhead. Only the proper con- structor has to run, not the default constructor and then whatever assignment operator is needed. The colon initializers should appear in the order that member variables are declared. That is the order in which C++ initializes member variables, and good compilers will give warnings if you try doing it in any other order, since the will reorder the initializations to follow what the standard says. o Don't provide an initializer for a member that you aren't going to provide a value for. o DO provide an initial value for any C members. These are the basic c/c++ non-class types such as pointers, integers, floats, bools, etc. o The colon initializer should be in the first column, followed by a space and the name(value) initializer for the first member, in order, to be initialized. If there are more initializers, follow the initializer with a comma, and then start a new line, beginning with two spaces for the next initializers. A::A(int i, double d) : my_integer_value(i), my_real_value(d) { } o If you define any virtual methods in a class, you must define a virtual destructor. If the top-level class where the virtual class starts at doesn't have anything to destruct, you must provide a null default destructor for it. class A { virtual ~A() { } }; o In most cases the copy constructor should initialize all its members to a known "null" state, and then use the assignment operator for the class to do the actual assignment. That guarantees that copy constructors and assignment operators are identical ... which they should be. There are some cases which might require seperate implementations, but there should be no sur- prises if someone switches from using a declaration and an assignment to just using a copy constructor. o If a class inherits from another class, remember that you need to run the copy constructor or assignment operator for the inherited-from classes as appropriate. class B : A { int _i2; }; B::B(const B &r) : A((const A &)r), _i2(r._i2) { } const B &B::operator=(const B &r) { *(A *)this = (const A &) r; _i2 = r._i2; return *this; } Your Mission, Should You Choose to Accept It Is to go through your portions of the Niagara code base. Identify classes that exhibit the above problems and omissions and fix them. If you find something you can't fix, make a note of it for later in the source code as well as to other people. That way another person won't stumble across the problem and wonder about what is going on. They will find the comment and know, or perhaps know much earlier in the game from the note.