C/C++ Why use initializers for constructor functions in C++ classes?

  • Thread starter Thread starter Math Is Hard
  • Start date Start date
  • Tags Tags
    C++ Classes
Click For Summary
Using initializers for constructor functions in C++ is important for efficient member variable initialization. The colon syntax allows for direct initialization of member variables before the constructor body executes, which can lead to performance improvements by avoiding unnecessary default construction followed by assignment. This is especially crucial for classes without default constructors, as it ensures that member variables are initialized correctly based on constructor parameters. Additionally, understanding the order of initialization is vital, as base class constructors are called first, followed by member variables in the order they are declared. Overall, using initializer lists is considered best practice for clarity and efficiency in C++ class design.
Math Is Hard
Staff Emeritus
Science Advisor
Gold Member
Messages
4,650
Reaction score
39
I'm working on learning how to create classes in C++ and I'm still a little unclear on the syntax for constructor functions and access member functions.
We've been using this class called "rational" for working with rational numbers. Header and implementation files are here.

http://www.math.ucla.edu/~rclark/10a.1.05w/hw6/my/rational.h
http://www.math.ucla.edu/~rclark/10a.1.05w/hw6/my/rational.cpp

My main question is: in the header file, the constructor function looks like this:

rational(int n=0,int d=1) : num(n), den(d) {reduce();}

It creates a variable with two int parts: one for the numerator and one for the denominator.

I don't understand what the colon means here. I know that the int n = 0 and d = 1 are default values, so does the colon mean use the default values unless the user has initialized a value for num or den or both?
And I don't really understand why this assignment of n to num and d to den goes on outside of the curly braces. The only thing he does in the curly braces is call a function to reduce the fraction.
Any comments are appreciated. I use MS VC++ version 6 if that matters.
Thanks.
 
Last edited by a moderator:
Technology news on Phys.org
Consider this conundrum:

You have a class that doesn't have a default constructor. For example:

Code:
class foo
{
public:
  foo(int number);
};

Now, normally when you'd use a `foo', you would call its constructor when declaring it:

Code:
int main()
{
  foo x(17);
}

In fact, you have to do so, because the `foo' class has no default constructor.

But what if you wanted to use a foo as a private variable? e.g.

Code:
class bar
{
private:
  foo x;
public:
  bar(int num);
};

You can't create an `x' without initializing it, so there is a problem... and you can't know just how to initialize it until you're in one of your constructors. (You might want to initialize `x' with `num + 1', for example)

That's what the colon syntax is for -- it is how you call the constructors for your member variables.

Beyond this problematic case, it is generally considered good practice to initialize your member variables with this initialization syntax when possible.
 
Thanks, Hurkyl. I guess it's just confusing to me because it doesn't look like what I expect a "function" to look like.
 
Oh, and a nitpick -- I can't see the code so I can't check, but shouldn't there be a test to ensure d != 0 in that constructor? Maybe reduce complains?
 
It's also necessary to use the colon syntax if you're using inheritance and you want to specify which parent constructor the child should call.

Basically, you use the colon syntax to specify any initialization that has to be done before the class is constructed. It's kind of messy, but it has some advantages.
 
Hurkyl said:
Oh, and a nitpick -- I can't see the code so I can't check, but shouldn't there be a test to ensure d != 0 in that constructor? Maybe reduce complains?

Reduce contains an assert(den != 0).
 
Why is it num(n) and den(d)? num and den are variables, not functions!
 
so-crates said:
Why is it num(n) and den(d)? num and den are variables, not functions!

Because in an initializer list you specify which constructors to use for class members. Of course, primitive types like int don't really have constructors, but you're allowed to initialize them as if they had copy constructors.
 
It's also important to know the order in which the initializers will be evaluated. For example, the following definition will cause problems:
Code:
class Thingy : public BaseThingy {
public:
    Thingy(int endpoint_, int count_) : last(endpoint_), BaseThingy(), first(last - count_) { }
private:
    int first;
    int last;
};
The reason this is a problem is that initializers in the constructor are not executed in the order they are listed, but rather the base class is always initialized first, followed by the other member variables in the order those variables are declared in the class definition. (Then the body of the constructor will be executed after all the members are initialized.) Thus the three initializers in the constructor for Thingy will be executed in this order:
  1. BaseThingy() // The base class constructor
    [*]first(last - count_)
    [*]last(endpoint_)
The problem occurs when initializing the member first, as the expression calculating the value for first contains a reference to last—but last has not been initialized yet!

The recommended practice is to always list the initializers in the order they will be executed.
 
  • #10
plover said:
Code:
class Thingy : public BaseThingy {
public:
    Thingy(int endpoint_, int count_) : last(endpoint_), BaseThingy(), first(last - count_) { }
private:
    int first;
    int last;
};

Better to avoid writing the derived class constructor that way! If there is a long list of attributes to initialize one should rather write the body of the constructor.
Besides, you don't have to write the superclass constructor BaseThingy() when it is without arguments.

Code:
class Thingy : public BaseThingy {
public:
   Thingy(int endpoint_, int count_) 
   {
       last = endpoint_;
       first = last - count_;
   }
private:
    int first;
    int last;
};
 
  • #11
ramollari said:
Better to avoid writing the derived class constructor that way! If there is a long list of attributes to initialize one should rather write the body of the constructor.
Besides, you don't have to write the superclass constructor BaseThingy() when it is without arguments.

Why is it better to initialize things inside the constructor? I agree that including calls to default constructors is rather pointless, but this rule about where you "should" put initialization sounds like your personal preference.
 
  • #12
ramollari said:
Better to avoid writing the derived class constructor that way! If there is a long list of attributes to initialize one should rather write the body of the constructor.
Besides, you don't have to write the superclass constructor BaseThingy() when it is without arguments.
This was a just contrived example to show why knowing how initialization order works can be important. It is, of course, true that the BaseThingy constructor is not necessary, and I probably should have said that. The reason I included it was to show the different things that appear in an initializer list.

However, I disagree that having a long list of initialized attributes is necessarily bad. For example, const and reference members must have initializers. Also, for many C++ experts (see for example Scott Meyers' Effective C++, Item #12), using initializers for member variables is the preferred practice for cases where an initializer is possible and the expressions necessary for producing the constructor arguments don't make the code unsafe or unreadable. The main reason for this is efficiency.

Suppose you have the following:
Code:
class StringyThingy { 
public: 
    StringyThingy(std::string s_) { theString = s_; } 
 
private: 
    std::string theString; 
};
So what happens when a StringyThingy gets constructed? Well, all members will be initialized before the body of the constructor is executed. So first, the default string constructor will be called for theString. Then when the body of the constructor is executed, the desired value for theString will be assigned via the assignment operator. If an initializer were present setting theString to s_, then the copy constructor would be called directly.

In many programs, this optimization will make no real difference, in others it will. To me, it's simpler to just use initializers consistently and not worry about it.
 

Similar threads

  • · Replies 36 ·
2
Replies
36
Views
6K
  • · Replies 25 ·
Replies
25
Views
2K
  • · Replies 22 ·
Replies
22
Views
3K
  • · Replies 2 ·
Replies
2
Views
2K
  • · Replies 23 ·
Replies
23
Views
3K
Replies
89
Views
6K
  • · Replies 36 ·
2
Replies
36
Views
3K
  • · Replies 35 ·
2
Replies
35
Views
4K
  • · Replies 36 ·
2
Replies
36
Views
3K
  • · Replies 9 ·
Replies
9
Views
2K