1

So, I'm kind of a newbie in c++, so let me explain my problem and how I understand it and please correct me if I'm wrong.

I have a base class that represents ui buttons. It has methods like onClick, mouseOver and members like label. Also, it has a rect member with the data of the "rectangle" representing the button: its x and y position in the window and its width and height.

Simplified code:

#include <cstdio> 

struct Rect {
  int x { 0 };
  int y { 0 };
  int w { 0 };
  int h { 0 };
};

class BaseButton {
  protected:
  const int width { 0 };
  const int height { 0 };
  Rect rect;

  public:
  BaseButton(int x, int y) {
    rect = Rect { x, y, width, height };
  }

  void debugRect () {
    printf("x: %d, y: %d, w: %d, h: %d\n", rect.x, rect.y, rect.w, rect.h);
  }
};

Now, BaseButton can be extended by other classes, like CheckboxButton or SendButton. You can place each button at a different (x, y) position, but every instance of CheckboxButton has a width and height of 16, while every SendButton has a height of 16 and a width of 32:

class CheckboxButton: public BaseButton {
  protected:
  const int width { 16 };
  const int height { 16 };

  public:
  using BaseButton::BaseButton;
};

class SendButton: public BaseButton {
  protected:
  const int width { 32 };
  const int height { 16 };

  public:
  using BaseButton::BaseButton;
};

If I debug it, the result is this:

int main () {
  CheckboxButton cbb { 10, 10 };
  cbb.debugRect(); // x: 10, y: 10, w: 0, h: 0
}

The difference from what happens in some other OOP languages is that the base class constructor has no access to the derived class variables. In python, for instance, the code would be like this:

class Rect:
  def __init__(self, x, y, w, h):
    self.x = x
    self.y = y
    self.w = w
    self.h = h

class BaseButton:
  width = 0
  height = 0

  def __init__(self, x, y):
    self.rect = Rect(x, y, self.width, self.height)

  def debug_rect(self):
    print "x: {0}, y: {1}, w: {2}, h: {3}".format(self.rect.x, self.rect.y, self.rect.w, self.rect.h)

class CheckboxButton(BaseButton):
  width = 16
  height = 16

class SendButton(BaseButton):
  width = 32
  height = 16

cbb = CheckboxButton(10, 10)
cbb.debug_rect() # x: 10, y: 10, w: 16, h: 16

I would like to reuse the base class constructor's code as much as I can. So, basically, "configure" the derived class parameters and have the constructor code use them to make the instances of each type of button.

One solution I can think of it to have the base class constructor accept all the parameters it needs and overload such constructor from the derived classes with fixed parameters, like this:

class BaseButton {
  Rect rect;

  public:
  BaseButton(int x, int y, int w, int h) {
    rect = Rect { x, y, w, h };
  }

  void debug_rect () {
    printf("x: %d, y: %d, w: %d, h: %d\n", rect.x, rect.y, rect.w, rect.h);
  }
};

class CheckboxButton: public BaseButton {
  private:
  using BaseButton::BaseButton;

  protected:

  public:
  CheckboxButton(int x, int y): BaseButton { x, y, 16, 16 } {};
};

int main () {
  CheckboxButton cbb { 10, 10 };
  cbb.debug_rect();
}

Another solution I can think of is using templates:

template<int W, int H>
class BaseButton {
  Rect rect;

  public:
  BaseButton(int x, int y) {
    rect = Rect { x, y, W, H };
  }

  void debug_rect () {
    printf("x: %d, y: %d, w: %d, h: %d\n", rect.x, rect.y, rect.w, rect.h);
  }
};

class CheckboxButton: public BaseButton<16, 16> {
  public:
  using BaseButton::BaseButton;
};

int main () {
  CheckboxButton cbb { 10, 10 };
  cbb.debug_rect();
}

These two solutions work, but my problem is that they don't scale elegantly. In my simplistic example, I'm just adding two arguments to the base class constructor and the constructor is just a couple of lines of code. What if the configuration parameters are 20 and the constructor is much more complicated than this?

What is the standard c++ way of solving cases like this? Thanks

pistacchio
  • 56,889
  • 107
  • 278
  • 420
  • 2
    Please have a look at this [C++ books](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) list. As-is your question is too long and the subject is too broad. Please try to narrow it down. – Ron Jan 13 '18 at 13:15
  • Why don't you let the `BaseButton` constructor take the width and height as arguments as well? Then you can make your own `CheckboxButton` constructor "call" the base constructor with the correct arguments in the `CheckboxButton` constructor initializer list. – Some programmer dude Jan 13 '18 at 13:18
  • If you look for some inspiration, you may go to [Qt doc](http://doc.qt.io/qtcreator/creator-writing-program.html). You will even have access to the source code (e.g. on [woboq.org](https://code.woboq.org/qt5/)) but I believe the API reference is more interesting for you. Btw. before Qt I used gtkmm, before: GTK+, before: OSF/Motif... Somehow, they all work similar. :-) – Scheff's Cat Jan 13 '18 at 13:20
  • So your question isn't really about the virtual-calls-during-construction idiom (it's a FAQ), but rather about how to handle a large number of arguments in GUI code. That's the named parameters idiom. Also a FAQ. – Cheers and hth. - Alf Jan 13 '18 at 13:21
  • @Someprogrammerdude This is the first solution that I show in the question. The problem arises when you don't have just two parameters (width and height) but 10 or 20, as I say in the last part of the question. – pistacchio Jan 13 '18 at 13:25
  • Why not have the base class constructor accept a `Rect` rather than a set of `int`s? Then the derived class constructors can all invoke that constructor, and modify any members of the `Rect` they like. The control is then in the derived class constructors (which, presumably from your description, have a purpose of modifying how the base class is constructed). – Peter Jan 13 '18 at 13:25
  • @Scheff The "Gui" code is just an example. I could have formulated it with a base "Enemy" class with a complex constructor and derived classes like Ork or EvilUnicorn, each having various params like strenght, speed, attacks and tens of other. – pistacchio Jan 13 '18 at 13:28
  • @Cheersandhth.-Alf The "Gui" code is just an example. I could have formulated it with a base "Enemy" class with a complex constructor and derived classes like Ork or EvilUnicorn, each having various params like strenght, speed, attacks and tens of other. – pistacchio Jan 13 '18 at 13:29
  • @Peter Thanks for you answer. As stated, this is just an example. The problem is when I don't just have to create a Rect instance (that, as you say, cone can pass as parameter), but when the base constructor has to execute complex algorithms and initializations on a multitude of parameters. – pistacchio Jan 13 '18 at 13:32
  • OK. I missed that point in your question - should've read more carefully. But, IMHO this doesn't change things really. Qt is open source. You can get inspiration from it if you like. Other (non-GUI) C++ APIs (I know) have a similar design. (GTK+ even resembles the OOP model with a C API.) The only big exception, I know, is the API of OpenGL (where you don't have something like object pointers but IDs instead which have to be bound before any retrieval or modification by a resp. GL function can be done). (OK, nobody has ever said the OpenGL API were OOP...) ;-) – Scheff's Cat Jan 13 '18 at 13:34
  • In your python code `width` and `heigh` are static members. Just do the same thing in you c++ code. In c++ you have to declare them with the `static` keyword. – Oliv Jan 13 '18 at 13:36
  • @Oliv can you prove sample code of your solution? Thanks – pistacchio Jan 13 '18 at 13:51
  • @pistacchio look at christophe answer, and if you care replace the litterals by static constants. – Oliv Jan 13 '18 at 13:58
  • @pistachio - the general approach is to not have the base class constructor execute complex algorithms and initialisation. Instead, it should only orchestrate construction/initialisation of its members and any bases. The idea of a base class is not to provide a complete set of services that derived classes might optionally use. It is to provide a minimal but consistent set of services that a derived class can modify or extend. In short: what you are describing is completely upside down relative to most useful approaches in C++. – Peter Jan 13 '18 at 14:01

1 Answers1

1

The problem

In the child you attempt to initialize members of the parent without using an explicit constructor. In trying to do that, you define new member variables in the derived class with the same name as members in the parent class, so that these have their name hidden:

class CheckboxButton: public BaseButton {
  protected:
  const int width { 16 };   // new CheckboxButton::width, 
                            //          leaves BaseButton::width unchanged
  const int height { 16 };  // leaves BaseButton::width unchanged
  ...
};

The debug function is defined in the base class and only knows the BaseButton::width and BaseButton::height, which are constants that remain unchanged at 0.

The solution

A good practice is to construct the member variables with a mem-initializer in the constructor, because these are used to construct the members:

class BaseButton {
protected:
  const int width;
  const int height;
  Rect rect;

public:
  BaseButton(int x, int y, int w, int h) : width{w}, height{h} {
    rect = Rect { x, y, width, height };
  }
  BaseButton(int x, int y) : BaseButton(x,y,0,0) {}  // delegate construction
                                                     // to other constructor

  ...
};

You see that there is a short constructor with two arguments, that delagate the construction to a constructor with four arguments, that allows to initialize height and width as well. Note by the way that the statement in the constructor body are executed after the mem-initializers.

The derived class would then call the appropriate base constructor:

class CheckboxButton: public BaseButton {
public:
  CheckboxButton (int x, int y) : BaseButton(x,y, 16,16) {}
};

If width and height would not be constants, you could have kept only one constructor for the base class, as in your original code and just assigned the new values in the body of the derived constructor.

Community
  • 1
  • 1
Christophe
  • 68,716
  • 7
  • 72
  • 138