0

I have the following code that runs just fine as long as I have the Rect default constructor included. However if I comment it out hope that it will just 'skip' to the Shape default constructor it fails to compile.

#include <cstdio>

class Shape
{
public:
    Shape()
    {
        printf("Shape default called\n");
    }
};

class Rect : public Shape
{
public:
    int width;
    int height;
    
    Rect()
    {
        printf("Rect default called\n");
    }

    Rect(int width, int height)
    {
        this->width = width;
        this->height = height;
    }

    int area()
    {
        return width*height; 
    }
};

class Square : public Rect
{
public:
    Square(int width)
    {   
        printf("Square constructor called.\n");
        this->width = width;
        this->height = width;
    }
};

int main(int argv, char** argc)
{
    printf("...\n");
    Square r = Square(10);
    printf("Area: %d\n", r.area());
    return 0;
}

When the program runs and compiles I get the following output

...

Shape default called

Rect default called

Square constructor called.

Area: 100

I was hoping to simply remove the Rect default constructor and get

...

Shape default called

Square constructor called.

Area: 100

because all the rect default constructor will do is sit there to satisfy clang's error. It isn't intended to do anything extra. Is there a way to accomplish this?

Community
  • 1
  • 1
Zimm3r
  • 3,369
  • 5
  • 35
  • 53

1 Answers1

2

You should use member initializer list syntax to call appropriate Rect constructor with your arguments:

Square(int width) : Rect(width, width)
{
    printf("Square constructor called.\n");
}

Notice that you don't need to manually assign to this->width and this->height, Rect constructor will do this for you.

Main advantage of using initializer list instead of assignment in constructor body, is that it allows to avoid unnecessary calls to default constructor. See this answer for detailed explanation.

It also allows you to inherit from (and have data members of) classes that don't have default constructor.

If you need to do something with the values first, it's perfectly ok to assign values in constructor body, but your base class (or member class, if you are initializing a data member) must have a default constructor.

The reason for this is that every data member's constructor must be (and is) called BEFORE your class' constructor body starts to execute. Initializer list allows you to choose which constructor gets called, depending on arguments you provide. If you don't initialize a data member in initializer list, it will be constructed using its default constructor (that is why it must be declared) before it can be assigned any value in constructor body.

Community
  • 1
  • 1
Alexander Revo
  • 719
  • 9
  • 15
  • So there is no way to not use member intializer list syntax and not have a constructor for Rect? – Zimm3r Jun 17 '15 at 17:52
  • 1
    Using member initializer list is the only way to pass an argument to a base class constructor, and is, in fact, the right way to do what you are trying to accomplish. It is also a preferred way to initialize class fields, so you should be using it in `Rect` constructor to set values of `width` and `height`, instead of `this->width = ...` and `this->height = ...` – Alexander Revo Jun 17 '15 at 17:57
  • I am going to disagree with Alexander Revo. His answer is the best way to handle this, but @Zimm3r , you can do what you want. However, this puts the onus on square to do things that the rectangle should do given your object hierarchy. Best practice is to allow squares to do square things and rectangles to do rectangle things. You and future maintainers of your software will have less surprises that way. Important point: There will be a rectangle constructor even if it does nothing and you remove the code for it. – user4581301 Jun 17 '15 at 18:18
  • @user4581301 I agree and this is a simple example but sometimes there may be things you want to do BEFORE you call the constructor. – Zimm3r Jun 17 '15 at 18:30
  • @Zimm3r OK. Rethought. Square(int width) : Rect(FreeFunctionOrStaticMethodThatDoesStuffThenReturnsParam(width), width). Or better, add an init(int width, int height) function to Rect. That way Rect can retake full control of its data. At the very least, change Rect's width and height to protected access. – user4581301 Jun 17 '15 at 18:47
  • I edited my answer to provide a more detailed explanation. – Alexander Revo Jun 17 '15 at 19:05
  • @user4581301 default constructor is not implicitly generated if there is another constructor declared in the class (unless you tell the compiler to generate it by using C++11 keyword `default`), so in this case there will be no default constructor for `Rect`. – Alexander Revo Jun 17 '15 at 19:08