Jason
Jason

Reputation: 35

Member initialization in constructor when the member has another class as its type

I recently started learning C++ and have been using cplusplus.com's tutorials to learn. I got to the classes section, and saw how to initialize members without typing it in the constructor's body. This seemed simple at first, but their example left me confused. Here it is:

// member initialization
#include <iostream>
using namespace std;

class Circle {
    double radius;
  public:
    Circle(double r) : radius(r) { }
    double area() {return radius*radius*3.14159265;}
};

class Cylinder {
    Circle base;
    double height;
  public:
    Cylinder(double r, double h) : base (r), height(h) {}
    double volume() {return base.area() * height;}
};

int main () {
  Cylinder foo (10,20);

  cout << "foo's volume: " << foo.volume() << '\n';
  return 0;
}

The part that confuses me is the Cylinder class, specifically:

Cylinder(double r, double h) : base (r), height(h) {}

Earlier in the class, a member object called "base" of type Circle was declared. Then, in the cylinder constructor, this "base" object was set to "r", which is a double:

base (r), height(h) {}

My question is, how could the member "base" be initialized to a double, when it is of type "Circle"? Also, I tried doing what is (what I think is) essentially the same thing, but instead I initialized "base" in the constructor's body:

Cylinder(double r, double h)
{
    base = r;
    height = h;
}

This left me with the compilation error: "error: no matching function for call to 'Circle::Circle()'"

If someone could clear up this confusion for me, I would greatly appreciate it. Thanks.

Upvotes: 2

Views: 700

Answers (3)

Tim_King
Tim_King

Reputation: 135

Cylinder(double r, double h) : base (r), height(h) {}  

Code above constructs object using the initialization list, "base (r), height(h)", this is where the initialization of base really happens: constructor of base is called.

If you put base into body of constructor like this:

Cylinder(double r, double h)
{
    base = r;
    height = h;
}

as there is nothing in initialization list but initialization of base has to happen in this phase, compiler would call the no-parameter constructor of base to perform this. But you have not provided such a constructor but a constructor with one parameter, in this case compiler would not generate the constructor without parameter as well, so compiler is failed to find the constructor of base without parameter it is expecting and will raise error.

To solve this, you just need to provide a constructor without parameter for class Circle:

class Circle {
    double radius;
  public:
    Circle(double r) : radius(r) { }
    Circle():radius(1.0){} // just example
    double area() {return radius*radius*3.14159265;}
};

Then you code would work. But you may need to note what happens for "base = r" in the constructor body of Cylinder:

  1. it will call "Circle(double r) : radius(r) { }" to construct a temp object with Circle type by using "r" as parameter
  2. then it will perform the bit-wise copy to copy all thing in temp object to "base" as you don't provide assignment method.

Regarding to member "height" in Cylingder, as it is of primitive type, it usually OK to initialize it in initialization list, or leave it as it is in memory then assign it with a value you want in constructor body of Cylinder. But if you would like to let "height" have determined value before go to constructor body, you have to initialize it in initialization lit

Upvotes: 0

Rich
Rich

Reputation: 1851

Look at this link on member initialization list. Especially the following applies to your case:

1) Initializes the base or member named by class-or-identifier using direct initialization or, if expression-list is empty, value-initialization

and

Before the compound statement that forms the function body of the constructor begins executing, initialization of all direct bases, virtual bases, and non-static data members is finished. Member initializer list is the place where non-default initialization of these objects can be specified.

Specifically in your examples, base of type Circle is direct-initialized with a double in the member initializer list. But if base does not appear in the member initializer list, it is default-initialized. However, the compiler doesn't synthesize a default constructor for class Circle because there is other user defined constructors. That is why you see the error: "error: no matching function for call to 'Circle::Circle()"

You can create the default constructor with = default

Circle() = default;

Upvotes: 0

songyuanyao
songyuanyao

Reputation: 172894

how could the member "base" be initialized to a double, when it is of type "Circle"?

Member initialization list will call the appropriate constructor for base, and Circle has a constructor which take a double as parameter, so it works.

base = r; does compile too, because r can be implicitly converted to Circle by the above constructor, and then operator=(const Circle&) (generated by compiler implicitly) will be called. But without member initialization list base need to be initialized by the default constructor first, which Circle doesn't have. That's what error message says.

Upvotes: 2

Related Questions