Constructor Initializer

From Cppwiki

Jump to: navigation, search

A constructor initializer is a list of member and base class initializations for a particular class. To understand why this is an important part of object construction, we need to cover a little bit of how objects are constructed, and when members and base classes are initialized.

Objects are constructed from the bottom up. That is, if you drew a diagram of your object with base classes at the bottom and the most derived class at the top, the construction of that object begins at the bottom and moves upwards, only constructing a further derived class if the construction of its base was successful.

For example, consider an object called Button, which derives from Control, which derives from Window, the base class. When a Button is constructed, the first thing that happens is the initialization of Window and its members. Next, Control and its members are initialized, and finally Button and its members are initialized.

// Simple window base class, stores height and width
// information, and can be drawn.
class Window
{
public:
    virtual ~Window();
    virtual void Draw();
    int getHeight() const;
    int getWidth() const;
private:
    int height, width;
};

// Simple control class, can be enabled or disabled,
// and handles mouse click and mouse drag events.
class Control: public Window
{
public:
    virtual ~Control();
    virtual void Draw();
    virtual void OnMouseClick();
    virtual void OnMouseDrag();
    void Enable();
    void Disable();
private:
    bool enabled;
};

// Simple button class, overrides mouse click handler
// and provides a mechanism for setting button text.
class Button: public Control
{
public:
    virtual ~Button();
    virtual void Draw();
    virtual void OnMouseClick();
    virtual void SetText(const std::string &text);
private:
    std::string text;
};

Most people are familiar with using a constructor to set values into an object's member variables, but the key point to remember is that a constructor does not execute until the object has been fully constructed. Let's fill in where this takes place in the example above. First, Window's members are initialized, then Window's constructor executes. Next, Control's members are initialized, then Control's constructor executes. Finally, Button's members are initialized, and then Button's constructor executes.

What this means is that by the time the constructor for an object is executed, the object's members and base classes have already been fully constructed. When an object's members are being initialized during construction, the compiler relies on constructing them in default fashion. This means that native types are essentially uninitialized, and user-defined types are default constructed. What, then, of types that do not provide default construction? This is where constructor initializers come in.

The constructor initializer provides a mechanism for specifying the construction details of members and base classes. For example, to set member variables based on constructor arguments, you can initialize them with these values directly, rather than assigning them in the body of the constructor. The first method directly initializes a member to the given value upon its construction, whereas the latter assigns a new value into a member that was default-constructed when the object was initialized.

Now we can extend our Window class to contain some members that cannot be default-constructed, and use the constructor initializer to initialize these values explicitly.

// Simple window base class, stores height and width
// information, and can be drawn.
class Window
{
public:
    // Constructor to set initial height and width. Since
    // we have provided a user-defined constructor, the
    // compiler will no longer generate a default
    // constructor for us.
    Window(int h, int w);

    virtual ~Window();
    virtual void Draw();
    int getHeight() const;
    int getWidth() const;
private:
    int height, width;

    // For no particular reason, this windowing library stores
    // its version as a reference in the base class. This is
    // just so we get a simple member that cannot be default
    // constructed.
    long &wndVersion;
};

static const long wndVersion1 = 1;

Window::Window(int h, int w)
// Here is the initializer. These initializations
// all happen before the constructor runs. If a member
// is not initialized here, the compiler attempts to
// initialize it using default construction if the
// member supports it.
: height(h), width(w), wndVersion(wndVersion1)

// And the constructor body follows here
{
    // Nothing to do, all of the members are initialized.
}

An important point to note is that members and base classes are initialized in the order they appear in the class definition, not the order they appear in the initializer list, so it is generally good style to mimic the order found in the class definition. This helps avoid confusion for someone reading it.

Now in the previous example we gave the Window base class a user-defined constructor, which means the compiler will no longer generate a default constructor for us. This means that the compiler no longer has a way of default-constructing the Window base class when trying to build derived objects such as Controls and Buttons. We can also use an initializer list to provide arguments to base class constructors, either to construct the base when a default constructor is unavailable, as in this case, or to use a different constructor than the default even when such is available. Let's see how this works in the Control class.

// Simple control class, can be enabled or disabled,
// and handles mouse click and mouse drag events.
class Control: public Window
{
public:
    // For controls, we elect to allow default construction, and
    // we will provide a reasonable default value for member
    // variables and base classes when writing the initializer.
    Control();

    // We also provide a constructor that allows setting the initial
    // values for the window's height and width, and the initial
    // enabled state of the control.
    Control(int height, int width, bool en);

    virtual ~Control();
    virtual void Draw();
    virtual void OnMouseClick();
    virtual void OnMouseDrag();
    void Enable();
    void Disable();
private:
    bool enabled;
};

// In a real world windowing library, you probably wouldn't provide
// default values for a window size like this, but this is just to
// illustrate that you can default construct a derived class even if
// its base requires arguments, by supplying those arguments in the
// initializer for the derived class.
Control::Control()
: Window(10, 40), enabled(true)
{
    // Again, nothing to do, everything's initialized
}

// It is perfectly legal and legitimate to pass constructor arguments
// to a base class when initializing.
Control::Control(int height, int width, bool en)
: Window(height, width), enabled(en)
{
    // Again, nothing to do, everything's initialized
}