3

Why am I not allowed to use the following syntax to call a constructor of a member object/different class in the body of the constructor of a class?

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;}
};

By the way I know I can call Circle::Circle(double) via Cylinder(double,double) using member initialization list like Cylinder(double r,double h) : base(r), height(r) {} but still what's wrong with the former method that the compiler is generating this error?

Error Message

Zaid Khan
  • 786
  • 2
  • 11
  • 24
  • 1
    @Khan: [Should my constructors use “initialization lists” or “assignment”?](https://isocpp.org/wiki/faq/ctors#init-lists) – legends2k Aug 22 '15 at 07:27

2 Answers2

7

The problem is that when C++ begins the execution of the constructor code all the member variables must have been already constructed (what if you for example call a method of Circle before constructing it?).

If immediate construction is a problem then a possible solution is to add to your member a default constructor and the using assignment in the body of the constructor of the containing class.

You can imagine that native types like int or double do have a default constructor, that's why you can initialize them later (note however that for one of the many ugly quirks of the language the default constructor for an int or a double doesn't actually do anything in this case and you're not allowed to do anything with such a member except assigning it a value - for example reading it is not allowed).

You cannot use base(r) in the body because that's not a valid statement... following a name with an opening parenthesis is only used for function call, for initialization in a declaration or for member initialization in the constructor member initialization list.

If you provide Circle with a default constructor then you can do

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

Note however that the approach of constructing non-working objects to fix them later is not the best approach for C++. The language likes the idea that if an object is constructed then it's usable and deviations from this are considered only when necessary (C++11 drifted away a bit from this original path with move constructor... but that's another story).

6502
  • 112,025
  • 15
  • 165
  • 265
7

The correct way to handle this is an initializer list. Write the constructor of Cylinder like this:

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

The part between the : and the opening brace is the initializer list that constructs all the data members of the class before the body of the constructor code is run. That way, C++ ensures that your object is fully constructed even within the body of the constructor.

Since C++ ensures that all members are fully constructed before the body of the constructor is run, any attempt to call a constructor from the body of a constructor would reinitialize the object. While technically possible using placement-new, this is almost certainly not what you want to do.

The syntax base(r); within the body of the constructor tries to call the operator()(double) on the already fully constructed member. Since you didn't provide such an operator, your compiler complains.

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
  • _Since C++ ensures that all members are fully constructed before the body of the constructor is run...._ Isn't the job of a constructor to 'construct' the members of its object? Is member initialization a C++11 thing? – Zaid Khan Aug 24 '15 at 13:33
  • @KhanZaid Yes, and no. Yes it is the job of the constructor to initialize the members, but no, it is not the job of the constructor's *body* to do so. The body may do additional stuff with the members *after* their construction, but their construction is strictly the job of the initializer list. This basic principle holds true for all C++ standards. Note that the default constructors of the built-in types (like `int`, `float`, and `Foo*`) don't do anything, leaving the value of the members undefined. These can be "initialized" within the constructor's body. But that's really a special case. – cmaster - reinstate monica Aug 24 '15 at 16:44