Flylib.com

Books Software

 
 
 

FAQ 22.03 What s the guideline for using initialization lists in constructor definitions?

FAQ 22.03 What's the guideline for using initialization lists in constructor definitions?

As a general rule, all member objects and base classes should explicitly appear in the initialization list of a constructor. In addition to being more efficient than default initialization followed by assignment, using the initialization list makes the code clearer since it takes advantage of something that the compiler is going to do anyway.

Note that there is no performance gain in using initialization lists with member objects of built-in types, but there is no loss either, so initialization lists should be used for symmetry.

For an exception to this guideline, see FAQ 22.11.

FAQ 22.04 Is it normal for constructors to have nothing inside their body?

Yes, this happens frequently.

The body of a constructor is the {...} part. A constructor should initialize its member objects in the initialization list, often leaving little or nothing to do inside the constructor's body. When the constructor body is empty, it can be left empty, perhaps ( { } ), or decorated with a comment such as

// Intentionally left blank.

An example follows ( Fract is a fraction class).

#include <iostream>
using namespace std;

class Fract {
public:
  Fract(int numerator=0, int denominator=1) throw();
  int numerator()   const throw();
  int denominator() const throw();
  friend Fract operator+ (const Fract& a, const Fract& b) throw();
  friend Fract operator- (const Fract& a, const Fract& b) throw();
  friend Fract operator* (const Fract& a, const Fract& b) throw();
  friend Fract operator/ (const Fract& a, const Fract& b) throw();
  friend ostream& operator<< (ostream& ostr, const Fract& a) throw();
protected:
  int num_;  //numerator
  int den_;  //denominator
};

Fract::Fract(int numerator, int denominator)
: num_(numerator)
, den_(denominator)
{ }

int Fract::numerator()   const throw() { return num_; }
int Fract::denominator() const throw() { return den_; }

ostream& operator<< (ostream& ostr, const Fract& a) throw()
{ return ostr << a.num_ << '/' << a.den_; }

int main()
{
  Fract a;                cout << "a = " << a << endl;
  Fract b = 5;            cout << "b = " << b << endl;
  Fract c = Fract(22,7);  cout << "c = " << c << endl;
}

The output of this program follows.

a = 0/1
b = 5/1
c = 22/7

Notice that the initialization list resides in the constructor's definition and not its declaration (in this case, the declaration and the definition are separate).

FAQ 22.05 How is a const data member initialized ?

Nonstatic const data members are declared in the class body with a const prefix, and their state must be initialized in the constructor's initialization list. The value used to initialize the const data member can be a literal value, a parameter passed to the constructor, or the result of some expression. After initialization, the state of a const data member within a particular object cannot change, but each object can initialize its const data member to a different value.

In the following example, i_ is a non- const member variable and j_ is a const member variable.

class Fred {
public:
  Fred(int i)        throw();
  Fred(int i, int j) throw();
protected:
  int i_;
  const int j_;
};

Fred::Fred(int i) throw()
: i_(i)
, j_(10)

<-- 1

{ }

Fred::Fred(int i, int j) throw()
: i_(i)
, j_(j)

<-- 2

{ }

int main()
{
  Fred a(5);      //a.j_ will always be 10
  Fred b(5,15);   //b.j_ will always be 15
  Fred c(5,20);   //c.j_ will always be 20
}

(1) Initialize const member j_ with a literal value

(2) Initialize const member j_ with a constructor parameter