February 1999

Constructing constructors

by Kent Reisdorph and Jody Hagins

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:
bullet Default constructor
bullet Copy constructor
bullet Overloaded constructors
In this article, we'll discuss the various types of C++ constructors, their benefits, and their usage.

Order of execution

Before explaining the various constructors, it's important that we understand what happens when a constructor, of any kind, is called. Before the code contained in a constructor is called, several things take place. First, if the class is derived from another class, a constructor is called to initialize the base class. Next, each member variable in the class is constructed. Finally, the constructor function itself is called. When you declare a constructor, you have control over how each phase is executed. If the constructor doesn't have an initializer list (see the article, "Constructor initializer lists"), then the compiler will generate code for each phase. If the compiler has to generate a call to a base class constructor, it will always generate a call to the base class' default constructor. This can result in subtle bugs, and is explained fully in the section on constructors in derived classes. If the compiler has to generate calls to member variable constructors, the compiler will always construct the member variable with its default constructor.

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.

The default constructor

The purpose of a constructor is to perform any initialization required by the class. The default constructor, however, takes no parameters, which means that it constructs every instance in the same (default) way. Here's an example of a class that declares a default constructor:
 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.

The copy constructor

The purpose of the copy constructor is to construct an instance of the class with the data contained in an existing instance of the class. A copy constructor has this function signature:
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 code

MyClass 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.

Overloaded constructors

This is the most common type of constructor for most users. An overloaded constructor is simply a constructor that you define in order to facilitate initialization of the class. Consider our class thus far. It has data members called x and y and a method called SetXY to initialize those data members. It also has a data member called data and a method called SetData. We can create a constructor that will initialize all of these data members at one time. It looks like this:
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).

Constructors in derived classes

One of the most powerful features of C++ is the ability to reuse functionality through derivation. However, this language feature is also responsible for some critical misunderstandings, and thus, bugs. A derived class is the same as its base class, with some extra functionality and/or data. So, when a derived class is constructed, its base class constructor will be called first, followed by construction of the class data members. Finally, the statements in the constructor function block are executed. The big issue is which base class constructor is called when a derived class constructor is executed. Before we get into the special situations, let's dispense with the easy part. Let's assume that we don't define any constructors in the derived class. As we learned earlier, in this case the compiler will generate a default constructor and a copy constructor for the class. The compiler-generated default constructor will construct the base class by calling the base class' default constructor. Likewise, the compiler-generated copy constructor will construct the base class by calling the base class' copy constructor, passing the rhs argument. This behavior is what we would expect.

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; }

 

Virtual constructors?

The C++ language doesn't support virtual constructors. Yet if you look at the TForm declaration in FORMS.HPP you'll see something like this (some superfluous code removed for clarity):
__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).

Conclusion

A well-written class allows full flexibility. By providing a default constructor, a copy constructor, and overloaded constructors, you control how your class functions. While the compiler does a fair job of providing default and copy constructors, you can maintain complete control over your class by writing your own constructors.