关于C++:如何处理”super”调用和递归

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"应该立即更新小部件的所有图形部分(并重新绘制):该函数递归调用超级类以执行更多的通用功能,并且还必须递归调用容器以传播更改(小部件的尺寸和位置可能会更改)。

enter image description here

每个小部件都应该"像这样"工作并且可以扩展,这就是这些需求的原因。

代码很广泛(大约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
    32
    class 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];
    };

对于您的更新功能,我建议使用一个模板方法update来递归更新复合模式,另一个方法updateThis只更新单个对象。


根据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;}
};


对于第一个例子,

Node::getNumValues()计算树的某些功能。

ExtNode::getNumValues()计算树的另一个函数。任何一个结果

ExtNode::getNumValues()( Node::getNumValues(), tree )的函数,或者只依赖于树。

对于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的调用"。