This is actually part 2 of the series. C++, like most programming
languages, starts counting at zero. Part 0 of the series is
here . I had intended to make Part 1 about serialization of
objects, but that can wait. If you can’t get past constructors in C++,
you won’t get very far. I have stumbled over some of the not-so-obvious
mistakes that you can make when constructing objects. Perhaps you can
benefit from my mistakes and avoid your own.
First, some technical tips and traps. Given a class Spoon
, which is correct?
Spoon* spoon = new Spoon();
or
Spoon* spoon = new Spoon;
The answer is either construction, in this case. There are other times
when you should use Spoon()
vs Spoon
. For example, you must use Spoon()
if you are using a temporary variable:
Spoon copySpoon(Spoon s) {
return Spoon();
}
or:
Spoon s = Spoon(); // uses copy constructor to create a Spoon, no
// different from, possibly worse than
// Spoon s;
However, note that you must leave off the parentheses in this example:
Spoon s; // constructs a Spoon s
Spoon s(); // function declaration, s() returns a Spoon!
Similarly, use Spoon()
in an initializer list:
class TableSet {
public:
TableSet();
private:
Spoon spoon;
};
TableSet::TableSet(Spoon s) : spoon(Spoon()) {}
This brings up another confusing point, i.e. variable initialization.
Remember auto variables, i.e. variables within a function, are not
automatically (even though they are called auto variables) initialized
(more precisely, they are not defined automatically when declared):
int foobar() {
int a; // garbage
return a;
}
cout << a; // you just printed garbage
Global variables (which you should never use, right?) are automatically initialized:
int a; // it's zero
cout << a; // you just printed 0
Static variables are also initialized, even in a function::
int foobar() {
static int a; // it's zero too, you get the point
return a;
}
Static variables in a class however should be manually initialized, but
not in the constructor or class definition:
class Spoon {
static int numSpoons;
Spoon();
};
int Spoon::numSpoons = 0;
Here you could leave out the initialization to zero, but nevertheless you need to have a int Spoon::numSpoons;
statement somewhere. Well wait a minute, if you are using int as a type, you actually can initialize in the class definition. But only for simple types:
class Spoon {
static int numSpoons = 0; // ok but look below
//static Tines tines = Tines(); // this won't compile
static Tines tines; // it's ok
};
int Spoon::numSpoons;
Tines Spoon::tines = Tines();
Here you still need the external int Spoon::numSpoons;
definition (but
without the = 0
!) despite the fact that it appears you have both declared and
defined numSpoons
in the class definition. But you can’t define
anything other than simple types in this way. And, even though
supposedly you can leave out the int Spoon::numSpoons;
and still have
this work “as long as you don’t take the address of numSpoons
“, according
to what I have read, I have found this doesn’t seem to work, at least
with my compiler, so I always put it in. The latter should work when
numSpoons
is a constant (static const int numSpoons = 0
), but I have not
found it to be the case. For the latter situation, the so-called “enum
hack” is useful:
class Spoon {
enum {numSpoons = 0};
};
Finally, data members in a class are not automatically initialized,
unless they are classes. This is very tricky. For example:
class FooBar {
public:
FooBar();
private:
int n;
Spoon spoon;
Spoon* spoonPointer;
};
FooBar::FooBar() {}
Well guess what? If you do this, n
is garbage, spoonPointer
points to
garbage, but spoon
is ok. This is also the case if you don’t declare a
FooBar()
constructor in the class, because C++’s automatically generated
constructor also doesn’t initialize these data members. You must
initialize these variables yourself:
FooBar::FooBar() : n(0), spoon(Spoon()), spoonPointer(0) {}
Note that the spoon(Spoon()
) is not necessary, but it doesn’t hurt to
initialize all data members so as not to forget any.
There are other technical nuances for constructors, such as the
advantage of initialization lists rather than initializing in the
constructor body, the order of initialization, initializing const and
reference variables, using the explicit keyword, but too much for a
single blog entry!
Just a bit more. Moving beyond the technical, and at the same time
trying to keep this blog entry to a reasonable length, some advice on
designing constructors. First, keep constructors simple. Do enough to
get the thing going, but that’s all. Remember a constructor assigns
memory for an object and then initializes it. That should be it in most
cases. If you have to load data from files or databases, which might
trigger an exception, do it in a separate public member function. You
don’t want your constructor throwing an exception. If so you are left
with no object, and most programs have a problem with that. So don’t do
anything that would throw an exception in your constructor.
More advice to follow in Part 2.
All that coding nonsense in this post looks remarkably similar to something evil I’ve been wrestling with on my site. Do you know anything about cascading style sheets?