如何在基类构造函数中使用派生类成员
Posted
技术标签:
【中文标题】如何在基类构造函数中使用派生类成员【英文标题】:How to use derived class members in a base class constructor 【发布时间】:2018-01-13 13:13:00 【问题描述】:所以,我是 C++ 的新手,所以让我解释一下我的问题以及我是如何理解它的,如果我错了,请纠正我。
我有一个代表 ui 按钮的基类。它有onClick
、mouseOver
等方法和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
可以被其他类扩展,例如 CheckboxButton
或 SendButton
。您可以将每个按钮放置在不同的 (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::width
和BaseButton::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
...
;
你看到有一个带有两个参数的短构造函数,它将构造委托给一个带有四个参数的构造函数,它也允许初始化height
和width
。注意构造函数主体中的语句在 mem-initializers 之后执行的方式。
然后派生类将调用适当的基础构造函数:
class CheckboxButton: public BaseButton
public:
CheckboxButton (int x, int y) : BaseButton(x,y, 16,16)
;
如果width
和height
不是常量,您可以只为基类保留一个构造函数,就像在原始代码中一样,并在派生构造函数的主体中分配新值。
【讨论】:
以上是关于如何在基类构造函数中使用派生类成员的主要内容,如果未能解决你的问题,请参考以下文章