How to deal with “super” calls and recursion
我的问题是关于合并两种技术:
- 递归调用超函数
- 递归调用同一函数
假设一个具有递归函数(foo)的根类和一个重写此函数(foo)的扩展类:重写函数必须调用super::foo,但需要在递归调用之前执行其他操作。
我将尝试一个例子(它只是一个例子,我知道有非递归的方法来解决这个问题)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | class Node { public: // must be override virtual int getNumValues() { if (parent) return parent->getNumValues() + 3; else return 3; } protected: Node *parent; private: int values[3]; }; class ExtNode: Node { public: //@override virtual int getNumValues() { int aux = Node::getNumValues(); //but need to avoid recursion here. if (parent) return parent->getNumValues() + aux + 2; else return aux + 2; } private: int extValues[2]; }; |
所以我想说的是:
- 我可以同时更改两个类:node和extnode。
- 我不会将代码从第一个类方法复制到第二个,以避免超级调用(类链可能很长)
- 递归调用可能由最子类完成
我正在尝试一些想法,但它们似乎编程实践不佳或不可能:
1 2 3 4 5 6 7 8 9 10 11 | // In Node class ... virtual int getNumValues() { if (parent && !isNodeObject(this)) return parent->getNumValues()+3; else return 3; } bool isNodeObject( Node *ob) { //return if ob is instance of Node (and not an extended class). How? } |
我还尝试使用可选参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // In Node class ... virtual int getNumValues( bool recursion = true) { if (parent && recursion) return parent->getNumValues()+3; else return 3; } // In ExtNode class ... virtual int getNumValues( bool recursion = true) { int aux = Node::getNumValues(false ); if (parent && recursion) return parent->getNumValues() + aux + 2; else return aux + 2; } |
最好的编程实践是什么?
编辑1:我试图解决的实际问题的解释(约阿希姆·皮尔伯格问)
我正在创建一个用户界面库,也就是一组类和函数,可以轻松创建框架、按钮、输入文本等小部件。
我已经创建了一个基本的(根类)小部件,它具有大多数通用特性,一个"可见"小部件,用于实现具有可见部分的小部件的所有通用功能,并一直运行。
还有一些容器,如框架、布局和窗口。
现在是最困难的部分:有一个函数"updatestyle"应该立即更新小部件的所有图形部分(并重新绘制):该函数递归调用超级类以执行更多的通用功能,并且还必须递归调用容器以传播更改(小部件的尺寸和位置可能会更改)。
每个小部件都应该"像这样"工作并且可以扩展,这就是这些需求的原因。
代码很广泛(大约8K行),还有很多其他特性,所以这里没有复制的必要代码。
看起来您正在搜索模板方法模式:
- 在基类中,实现一个概述函数一般行为的非虚拟方法。
- 定义(抽象)虚拟方法,定义该函数内部的特殊行为部分
在派生类中,重写特殊行为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32class Node
{
public:
int getAllNumValues()
{
int allNumValues = getNumValues();
if (parent) allNumValues += parent->getAllNumValues();
return allNumValues;
}
protected:
virtual int getNumValues() {
return 3;
};
private:
Node *parent;
int values[3];
};
class ExtNode: Node
{
protected:
//@override
virtual int getNumValues()
{
return 2 + Node::getNumValues(); //but need to avoid recursion here.
}
private:
int extValues[2];
};
对于您的更新功能,我建议使用一个模板方法
根据Herb Sutter的文章:虚拟性,我们应该更喜欢将虚拟功能设为私有的。这意味着我们应该尽量避免调用"超级"版本,而是让基类来完成工作。
下面是一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | class Node { public: int getNumValues() { int result = 3 + DoGetNumValues(); if (parent) result += parent->getNumValues(); return result; } private: Node *parent; int values[3]; virtual int DoGetNumValues() {return 0;} }; class ExtNode : public Node { private: int extValues[2]; int DoGetNumValues() override sealed {return 2 + GetMoreValues();} virtual int GetMoreValues() {return 0;} }; class Derived : public ExtNode { int GetMoreValues() override {return 1;} }; |
对于第一个例子,
对于UI问题,考虑责任链设计模式。将更新请求转发到根节点,然后根节点启动树遍历以更新从根节点开始的所有节点。
处理这一问题的一种方法是使函数非虚拟化,然后显式调用每个函数来重写超级类的函数(类似于构造函数)。
非虚方法意味着继承的每个类都有自己的方法实现,因此您不会通过编写函数实现来覆盖类的父代码。
缺点是,您必须通过显式地使用某种类型的指针调用函数,从而强制您了解该类型。
为了避免这个缺点,请创建一个调用所需递归函数的虚拟函数,并使用该函数。
另一方面,应避免使用非虚拟函数。
这是一个示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | class base { public: int doStuff() { printf(" base called"); return 0; } }; class ext : public base { public: int doStuff() { base::doStuff(); printf(" ext called"); return 0; }; }; class ext2 : public ext { public: int doStuff() { ext::doStuff(); printf(" ext 2 called"); return 0; }; }; void runTest() { base* ptr = new ext2(); ptr->doStuff(); ext2* recast = (ext2*) ptr; recast->doStuff(); } |
对于上述代码,输出将为"base called base called ext called ext2 called"。
如果在基类中声明dostuf函数为虚函数(从而使其对每个子类都为虚函数),则输出将为"名为ext2的基称为ext2的基称为ext2的基称为ext2的调用"。