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

  • Context: C/C++ 
  • Thread starter Thread starter Math Is Hard
  • Start date Start date
  • Tags Tags
    C++ Classes
Click For Summary
SUMMARY

The discussion focuses on the use of initializer lists in C++ constructor functions, specifically within a class called "rational" for handling rational numbers. The syntax "rational(int n=0,int d=1) : num(n), den(d) {reduce();}" initializes member variables num and den before the constructor body executes, allowing for efficient assignment and ensuring proper initialization order. It is established that using initializer lists is a best practice for initializing member variables, especially when dealing with complex types or inheritance. Additionally, the order of initialization is crucial, as base class constructors are called before member variables, which can lead to issues if not properly managed.

PREREQUISITES
  • C++ programming language fundamentals
  • Understanding of classes and constructors in C++
  • Knowledge of member initialization and default arguments
  • Familiarity with C++ inheritance and constructor chaining
NEXT STEPS
  • Study C++ initializer lists and their advantages over assignment in constructors
  • Learn about constructor initialization order and its implications in C++
  • Explore best practices for using default arguments in C++ constructors
  • Investigate the impact of using initializer lists on performance in C++ applications
USEFUL FOR

C++ developers, software engineers, and students learning object-oriented programming who want to enhance their understanding of constructor functions and efficient class design in C++.

Math Is Hard
Staff Emeritus
Science Advisor
Gold Member
Messages
4,650
Reaction score
36
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
3K
  • · Replies 22 ·
Replies
22
Views
4K
Replies
89
Views
6K
  • · Replies 2 ·
Replies
2
Views
3K
  • · Replies 23 ·
Replies
23
Views
3K
  • · 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