Short answer
That is a constructor with an empty body, and a member initialisation list that initialises two data members to the value 0
.
i.e.
class-name([ ctor-args ]) [ : member-init-list ] { [ ctor-body ] }
where member-init-list
is member-name(args) [, member-name(args) [, ... ] ]
.
(Note: not actual C++ lexical grammar constructions)
Long answer
Background
Take the following class definitions:
struct Base {};
struct Derived : Base {};
You may already be aware that Derived
derives from Base
.
You may also already be aware that Derived
and Base
both have a synthesised (implicitly-declared) default (takes no arguments) constructor. Derived
's constructor implicitly/automatically invokes Base
's.
Now let's add a basic member function:
struct Derived : Base {
void foo() {}
};
I have both declared and defined this member function foo
; its body is empty, so nothing happens when you invoke it. This is pretty pointless, but it's perfectly valid.
Now, instead, let's make our own constructor:
struct Derived : Base {
Derived() {}
};
This looks more familiar. It's still a [special] function with an empty body, and the Base
constructor is still being implicitly invoked. We haven't changed that.
Handling data members
Let's add some data members and set their value in our constructor:
struct Derived : Base {
Derived() {
x = 0;
y = 0;
}
int x, y;
};
Both x
and y
will have the value 0
after object construction. This is still fundamental C++.
But what you may not be aware of is that you are not initialising these data members. You are merely assigning to them after the possibility of initialising them.
In fact, data members of built-in types are not implicitly initialised, so let's pick a better example:
struct Derived : Base {
Derived() /* HERE */ {
x = "";
y = "";
}
std::string x, y;
};
x
and y
are implicitly initialised before the code in the constructor body runs. When the constructor body starts to run, all members that will be initialised, have already been initialised, and the base constructor has been implicitly invoked.
We can intercept this behaviour and provide our own initialisation values by writing a member initialiser list in the place where I wrote /* HERE */
in the previous snippet. With the member initialiser list, the snippet looks like this:
struct Derived : Base {
Derived() : x(""), y("") {
x = "";
y = "";
}
std::string x, y;
};
Wow! OK; now we're initialising the strings to ""
, and later assigning the same, empty value to them in the constructor body. We can get rid of those assignments, then:
struct Derived : Base {
Derived() : x(""), y("") {}
std::string x, y;
};
Now the constructor body is empty, but the constructor still does stuff. It implicitly invokes the base constructor, and it explicitly initialises the data members x
and y
to the empty string.
And, because std::string
has a default constructor that does the same, we can write for short:
struct Derived : Base {
Derived() : x(), y() {}
std::string x, y;
};
Going back to your original example, this applies to objects of built-in types just as easily. Let's consider two pointers:
struct Derived : Base {
Derived() : next(0), prev(0) {}
Derived* next;
Derived* prev;
};
Handling base classes
And, as an added bonus, we can use the member initialisation list to invoke the base constructor explicitly:
struct Derived : Base {
Derived() : Base(), next(0), prev(0) {}
Derived* next;
Derived* prev;
};
This is pretty pointless unless the base constructor wants some arguments:
struct Base {
Base(int x) {}
};
struct Derived : Base {
Derived() : Base(0), next(0), prev(0) {}
Derived* next;
Derived* prev;
};
I hope that this has been useful.