如何在基类构造函数中使用派生类成员

Posted

技术标签:

【中文标题】如何在基类构造函数中使用派生类成员【英文标题】:How to use derived class members in a base class constructor 【发布时间】:2018-01-13 13:13:00 【问题描述】:

所以,我是 C++ 的新手,所以让我解释一下我的问题以及我是如何理解它的,如果我错了,请纠正我。

我有一个代表 ui 按钮的基类。它有onClickmouseOver 等方法和label 等成员。此外,它还有一个 rect 成员,其中包含代表按钮的“矩形”数据:它在窗口中的 x 和 y 位置以及它的宽度和高度。

简化代码:

#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);
  
;

现在,BaseButton 可以被其他类扩展,例如 CheckboxButtonSendButton。您可以将每个按钮放置在不同的 (x, y) 位置,但CheckboxButton 的每个实例的宽度和高度都是 16,而每个 SendButton 的高度和宽度都是 16 和 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;
;

如果我调试它,结果是这样的:

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

与其他一些 OOP 语言的不同之处在于基类构造函数无法访问派生类变量。例如,在 python 中,代码是这样的:

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

我想尽可能多地重用基类构造函数的代码。因此,基本上,“配置”派生类参数并让构造函数代码使用它们来创建每种类型按钮的实例。

我可以想到的一种解决方案是让基类构造函数接受它需要的所有参数,并从具有固定参数的派生类中重载此类构造函数,如下所示:

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();

我能想到的另一个解决方案是使用模板:

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();

这两个解决方案有效,但我的问题是它们不能优雅地扩展。在我的简单示例中,我只是向基类构造函数添加了两个参数,而构造函数只是几行代码。如果配置参数是20,构造函数比这复杂很多呢?

解决此类案例的标准 C++ 方法是什么?谢谢

【问题讨论】:

请查看此C++ books 列表。你的问题太长了,主题太宽泛了。请尝试缩小范围。 为什么不让BaseButton 构造函数也将宽度和高度作为参数呢?然后,您可以使用 CheckboxButton 构造函数初始化器列表中的正确参数创建自己的 CheckboxButton 构造函数“调用”基本构造函数。 如果你在寻找灵感,可以去Qt doc。您甚至可以访问源代码(例如 woboq.org),但我相信 API 参考对您来说更有趣。顺便提一句。在 Qt 之前,我使用过 gtkmm,之前:GTK+,之前:OSF/Motif... 不知何故,它们的工作原理都相似。 :-) 所以您的问题实际上并不是关于 virtual-calls-during-construction 习语(这是一个常见问题解答),而是关于如何处理 GUI 代码中的大量参数。这就是命名参数的习语。还有一个常见问题解答。 @Someprogrammerdude 这是我在问题中展示的第一个解决方案。正如我在问题的最后一部分所说,当您不仅有两个参数(宽度和高度)而是 10 或 20 时,就会出现问题。 【参考方案1】:

问题

在子节点中,您尝试在不使用显式构造函数的情况下初始化父节点的成员。在尝试这样做时,您在派生类中定义与父类中的成员同名的新成员变量,以便隐藏它们的名称:

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

debug函数在基类中定义,只知道BaseButton::widthBaseButton::height,这两个是常量,保持为0不变。

解决方案

一个好的做法是在构造函数中使用 mem-initializer 构造成员变量,因为它们用于构造成员:

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

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

  ...
;

你看到有一个带有两个参数的短构造函数,它将构造委托给一个带有四个参数的构造函数,它也允许初始化heightwidth。注意构造函数主体中的语句在 mem-initializers 之后执行的方式。

然后派生类将调用适当的基础构造函数:

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

如果widthheight 不是常量,您可以只为基类保留一个构造函数,就像在原始代码中一样,并在派生构造函数的主体中分配新值。

【讨论】:

以上是关于如何在基类构造函数中使用派生类成员的主要内容,如果未能解决你的问题,请参考以下文章

调用派生类的构造函数在基类的构造函数之前执行

为啥在基类构造函数中看不到派生类属性值?

派生类(构造函数)中基类的成员变量初始化顺序

派生类在基类中删除时是不是会有隐式复制构造函数或赋值运算符?

在基类体内声明但通过派生类调用的函数的内联

C ++:如何在派生类中定义基类构造函数,如果基构造函数具有带有私有成员的初始化列表[重复]