Writing a good C++ class involves more than just adding a few methods and data members. If you want your classes to be both flexible and powerful, then you should write several constructors for them. Many programmers don't realize that they should have several constructors for their classes. A C++ class may have one or more of the following constructors:
Default constructor
| Copy constructor
| Overloaded constructors
| |
Thus, by the time your actual constructor code (the statements within the constructor code block) executes, you're insured that the base class and all members have been constructed. Note that built-in data types don't have constructors of any type, so their data must always be initialized before use.
class MyClass {
public:
MyClass();
int Multiply() { return x * y; }
void SetXY(int _x, int _y) {
x = _x;
y = _y;
}
private:
int x;
int y;
};
The definition of the MyClass constructor might look like this:
MyClass::MyClass()
{
x = 0;
y = 0;
}
In this case, the x and y variables are initialized to 0.
| Note: We're using inline functions for the Multiply and SetXY functions to shorten the listings. If you're unfamiliar with inline functions, see the C++Builder Help, under the topic "inline functions." We've made the constructors regular member functions to separate them from the class declaration. |
The C++ language requires every class to have at least one constructor. Thus, if the class doesn't explicitly declare at least one constructor, then the compiler will generate a default constructor. The following code, for example, compiles and runs just fine (note that no constructor is defined for the class):
class MyClass {
public:
int Multiply() { return x * y; }
void SetXY(int _x, int _y) {
x = _x;
y = _y;
}
private:
int x;
int y;
};
// later... MyClass c; Label1->Caption = c.Multiply();The results, however, might not be what you expect, since the compiler-generated default constructor does nothing at all. In fact, it will look very much like this:
MyClass::MyClass() { }
The default constructor, generated by the compiler, simply calls the default
constructor for each member variable in the class. However, built-in types
don't have constructors, so the values for x and y will be
uninitialized, and will contain whatever happened to be in that memory location
before the class was instantiated. By providing a default constructor you
control how your class is initialized.
The compiler will generate a default constructor only if you haven't declared a
constructor of any kind. Thus, it's possible to declare a class that has no
default constructor. At times, it may be appropriate to declare a class that
doesn't have a default constructor. However, such classes can't be created
without explicit constructor arguments. Thus, you can't easily create arrays of
the class, nor can you use the class in most template classes. In addition,
virtual base classes that don't have default constructors are a nightmare to
deal with.
MyClass(const MyClass& mc);Note that the copy constructor takes a const reference to the class itself. A simple copy constructor would look like this:
MyClass::MyClass(const MyClass& mc)
{
x = mc.x;
y = mc.y;
}
Here we make a simple member-by-member copy of the class' data members. (Some
C++ legalists argue that the copy constructor's parameter be named rhs, for
right hand side. Whether or not you follow that convention is up to you.)
As with the default constructor, the compiler will provide a copy constructor
if you haven't defined one. Unlike the rules for generating a default
constructor, the compiler will generate a copy constructor any time one isn't
explicitly declared (and the compiler detects the need for one), regardless of
how many other constructors are already declared. In fact, the previous example
of a copy constructor isn't necessary at all because it exactly duplicates what
the compiler-generated copy constructor would do for you, which is to do a
member-by-member copy by calling the copy constructor of each member.
The compiler-generated copy constructor is fine for simple classes, but it won't work properly for more complex classes. If you have any dynamically allocated data, for example, you should provide your own copy constructor to insure that the data is properly copied. In this case, you'd probably want your own default constructor, assignment operator, and destructor, as well (see the article entitled, "Concrete data types"). Consider the following example:
class MyClass {
public:
MyClass();
MyClass(const MyClass& mc);
int Multiply() { return x * y; }
void SetXY(int _x, int _y) {
x = _x;
y = _y;
}
char* GetData() { return data; }
void SetData(char* _data) {
free(data);
data = strdup(_data);
}
private:
int x;
int y;
char* data;
};
MyClass::MyClass(const MyClass& mc)
{
x = mc.x;
y = mc.y;
data = strdup(mc.data);
}
MyClass::MyClass()
{
x = 0;
y = 0;
data = strdup("");
}
Here we've introduced a data member called data. This member is a dynamically
allocated character array that's intended to hold a string value. Examine the
new copy constructor. Notice that it allocates memory for the string and copies
the value of the incoming instance's data using the strdup function (a C++
runtime library function). In addition, you'll note the use of free to release
the memory, since strdup is implemented with malloc, and mixing memory managers
is a no-no (a topic for another time).
Consider for a moment what would happen if we let the compiler generate the
copy constructor. The compiler would generate something like this:
MyClass::MyClass(const MyClass& mc)
{
x = mc.x;
y = mc.y;
data = mc.data;
}
In this case, we'd have this instance's data variable and the original
instance's data variable both pointing to the same location in memory. The
results would be undesirable, to say the least. Writing our own copy
constructor insures that each instance of the class has its own memory for the
data variable.
When does the compiler use the copy constructor? Basically, in three cases. The
first case is when an object instance is created from another object instance
of the same type:
MyClass a;
a.SetData("This is a test.");
MyClass b(a);
Label1->Caption = b.GetData();
The third line in this example constructs a new instance, b, from the first
instance, a, by passing a to the MyClass copy constructor. Note that the
following codeMyClass b = a;will use the copy constructor, as well. Some may think that the assignment operator would be called in this case; however, that's not true. The instance b doesn't exist, so nothing can be assigned to it. The compiler recognizes that b is being constructed and calls the copy constructor. Another case where the compiler uses the copy constructor is when an instance of the class is passed by value to a function. Here's an example:
void ShowData(MyClass c)
{
MessageBox(0, c.GetData(), "Message", 0);
}
void __fastcall
TForm1::Button1Click(TObject *Sender)
{
MyClass a;
a.SetData("This is a test.");
ShowData(a);
}
When the ShowData function is called, the compiler invokes the copy constructor
for the MyClass class. This only happens when a class is passed by value
because the compiler makes a copy of the class instance before calling the
function. When a class is passed by pointer or by reference this doesn't
happen, because the compiler doesn't make a copy of the instance in those cases.
The third case where the compiler invokes the copy constructor is when a
function returns an instance of a class. For example:
MyClass GetClass()
{
MyClass c;
// do some things with 'c'
return c;
}
Here, as with the previous case, the compiler makes a copy of c before
returning it to the caller.
Provide a copy constructor for your classes any time you need more control over
the copy process. This is particularly true anytime dynamic data is involved.
MyClass::MyClass(int _x, int _y,
char* _data)
{
x = _x;
y = _y;
data = strdup(_data);
}
Now, we can create an instance of our class and initialize all of the data in
one go:MyClass a(10, 20, "Hello");By adding default parameters, we can also make this constructor double as a default constructor. The new declaration would look like this:
MyClass(int _x=0, int _y=0, char* _data="");Now, we can create an instance of the class using any of the following:
MyClass a; MyClass b(10); MyClass c(10, 20); MyClass d(10, 20, "The text");If parameters are passed to the constructor then those parameters are used. If any of the parameter is omitted the default value for that parameter will be used instead. We've written our new constructor to use default parameters, so it now serves as the default constructor for the class. Since that's the case, we need to remove our previous default constructor from the class. If we don't remove the previous default constructor, the following code will generate a compiler error:
MyClass a;The compiler will complain about an ambiguity between the earlier default constructor and our new constructor (since either constructor can be called without parameters).
However, if you declare a constructor (of any kind) in the derived class, you must specifically call one of the base class constructors in the derived class' constructor initialization list. If you don't, then the compiler will automatically insert a call to the base class default constructor. This would be undesirable at best. Assume we want to derive a class from the MyClass class in the previous example. We might implement it like this:
class Derived : public MyClass {
public:
Derived() { i = 0; }
Derived(const Derived& d) { i = d.i; }
private:
int i;
};
At first, this may seem perfectly fine. However, there's a subtle bug lurking
about. Assume you have the following code:
Derived d1;
d1.SetData("Hello");
Derived d2(d1);
What's the value returned by d2.GetData()? It appears that it should be "Hello"
since d2 was copy constructed from d1. However, the value will actually be an
empty string. This is because the Derived copy constructor didn't explicitly
call one of the base class constructors. Anytime a derived class constructor
doesn't specifically call a base class constructor, the compiler will call the
base class default constructor, which, in most cases, isn't what you want.
Thus, for the above class, the copy constructor should be written as
Derived(const Derived& d)
: MyClass(d) { i = d.i; }
__fastcall virtual TForm(TComponent* AOwner);If C++ doesn't support virtual constructors what's the virtual keyword doing in this declaration? The Visual Component Library is, of course, written in Object Pascal and Object Pascal does have virtual constructors. As a result, Inprise made changes to the C++ language to support virtual constructors. This only applies to VCL classes; however, and classes you derive from VCL classes. You can't declare constructors as virtual for your regular C++ classes (nor would you ever want to).