Advertisement

Insights into member initialization

April 27, 2011

Dan_Saks-April 27, 2011

Often when it seems that C++ is generating bigger and slower code than C, it may be that C++ is actually just distributing generated code differently.

Click image to go to digital edition.
ESC Boston 2011 speaker logoAmong the most common reasons that C programmers offer to explain why they're disinclined to use C++ is that C++ does too much behind the scenes. A closely-related complaint is that C++ compilers generate too much code for seemingly simple expressions. If you look online at the reader comments on my columns over the last few years,1 you'll see remarks to that effect now and then.

Most of these complaints don't hold up well under scrutiny. Often, the alleged excess code simply isn't there. For example, function overloading and friendship are strictly translation-time facilities. They don't incur any run-time costs.

At other times, excess code appears only when targeting some processors and not others. For example, some processors are better than others at calling virtual functions. Even then, the code for calling a virtual function in C++ is usually about the same as calling a function through a pointer in C.

When the complaints do have merit, it's often that C++ isn't necessarily generating bigger and slower programs than C. It may be that C++ just distributes the generated code differently. It generates more code in some places and less in others. I believe that once you understand why C++ does what it does, the resulting code not only ceases to be surprising, but even becomes predictable. Such is the case with constructors.

A constructor is a special class member function that provides guaranteed initialization for objects of its class type. Since the beginning of the year, I've been explaining what constructors are in C++ and what kind of code they generate.2, 3 This month, I'll continue by explaining the interesting behavior of constructors for classes with members that have constructors of their own. As I often do, I'll illustrate the behavior using equivalent C code.

Class objects as members

Just as a C structure can have members that are themselves structure objects, a C++ class can have members that are themselves class objects. For example, let's look at a class for entries in some kind of symbol table, where each entry stores a name and some associated information.

To keep this simple, let's just say an entry has a name, an id, and a value. The name is the textual spelling of the entry's name. The id is an unsigned integer value that uniquely identifies each entry. The value is a sequence of one or more signed integer values associated with the name. The entry class definition looks in part like:
class entry
    {
    ~~~
private:
    string name;
    unsigned id;
    sequence value;
    };
Here, string is a class representing a variable-length string of characters. It might be the string class from the Standard C++ Library, or it might be a class custom built for this application. The sequence class represents a sequence of signed integer values. It might be a typedef name that's an alias for a Standard Library class template instantiation, such as:
typedef vector<int> sequence;

Then again, it might be a custom built class.

Now let's examine the behavior of various constructors for this entry class.

Generated default constructors
As I explained in my first article on constructors, a definition for a class object can specify a constructor argument list, as in:

entry e (n, v);

This defines e as an entry object. In this case, the compiler generates code that initializes e by calling a constructor that accepts n and v as arguments. If the entry class declares no such constructor, the compiler will blurt out nasty things.

In limited cases, the compiler may generate a constructor. For example, a definition for an object with no argument list, as in:

entry e;

invokes a particular constructor called the default constructor. The default constructor is special in that the compiler may generate it, but only if the class has no explicitly declared constructors at all.

If the compiler generates a default constructor for class entry, that default constructor calls the default constructor for each member of class type. In this case, the default string constructor would be called for member name, and the default sequence constructor would be called for member value. A C function that performs the same initialization as the generated default entry constructor might look like:

void construct_entry(entry *_this)
    {
    string_construct(&_this->name);
    sequence_construct(&_this->value);
    }

This function doesn't initialize the entry's id member, which has a non-class type and thus can't have a constructor.  Generated default constructors leave such members uninitialized.

Most compilers don't generate code for a default constructor unless the program actually uses that constructor.  Calls to a generated default constructor may be expanded inline.

< Previous
Page 1 of 2
Next >

Loading comments...