I'm new to C++, and the whole idea of classes - I'm still reading a book to try and learn. The book I'm reading says that when I construct a class, I can assign default values by doing this:
class foo {
public:
foo(char c, int i);
private:
char exampleChar;
int exampleInt;
};
foo::foo(char c, int i):
exampleChar(c),
exampleInt(i)
{}
This code (to me) looks very messy, and doesn't follow rules that I'm used to in other languages. My question is, what's the difference between doing the above, and this (below, which I personally think looks a lot cleaner)?
foo::foo(char c, int i) {
exampleChar = c;
exampleInt = i;
}
Sort of things I'm thinking about are: are there performance/efficiency issues if done on a large scale - or is it exactly the same?
The first way, by doing : exampleChar(c), exampleInt(i)
is called an initializer list.
If you do it the second way, the two variables are default constructed first1, then you assign them a value. (When the actual body of the constructor is entered, anything that hasn't been initialized by the initializer list is default constructed.) This is a waste of time because you're just overwriting the values anyway. For small types like int
or char
this isn't a big deal, but when those member variables are large types that would take lots of cycles to construct, you definitely want to use the initializer list.
The second way won't waste time giving them a default value and then overwriting it - it will set their values directly to that value you give it (or call the right constructor if the member is an object).
You can see what we mean by doing this:
class MyClass {
public:
int _i; // our data
// default constructor
MyClass() : _i(0) { cout << "default constructor"; }
// constructor that takes an int
MyClass(int i) : _i(i) { cout << "int constructor"; }
// assignment operator
void operator=(int i) { _i = i; cout << "assignment operator"; }
};
class OtherClass {
public:
MyClass c;
OtherClass() {
c = 54;
}
};
OtherClass oc;
You'll see that
default constructor
assignment operator
is printed. That's two function calls which, for other classes, could be expensive.
If you change the constructor of OtherClass
to
OtherClass() : c(54) { }
You'll see that
int constructor
is printed. Just one call compared to two. This is the most efficient way.
Initializer lists are also a must when you
have types that have no default constructor. You have to call the right constructor in the initializer list.
have a const
member that you want to give some value (rather than just have permantently the default value
have a reference member. You must use initializer lists on these.
tl;dr: do it because it's at least as fast but never slower than the other way, and sometimes far faster.
1 For built in types like int
and char
, they are actually not constructed at all; they just have the value of whatever memory they happen to have had previously.
The difference is that the compiler will always initialize all members (in declaration order) prior to the first user-defined constructor statement. In the case of a char
and an int
, which are both primitive types, 'initialization' actually means 'no initialization' here. However, if you have a member with a constructor that does some actual work, this constructor will be called upfront - if you do
foo::foo() {
myComplexMember = MyComplexClass(42);
}
the compiler did already invoke the MyComplexClass default constructor before your code got called, which is a waste of resources (and a compiler error if the default ctor is not accessible).
By using an initialization list, you can customize the default initialization and avoid doing things for nothing. Obviously, this is the way to go.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With