如何处理“超级”调用和递归

时间:2014-07-23 12:26:35

标签: c++ recursion super

我的问题是合并两种技术:

  • 递归调用超级函数
  • 以递归方式调用相同的函数

假设一个具有递归函数(foo)的根类和一个覆盖此函数的扩展类(foo):override函数必须调用super :: foo,但需要在递归调用之前执行其他操作。 / p>

我将尝试一个例子(这只是一个例子,我知道有非递归方式来解决这个问题)

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。
  • 我不会将第一类方法的代码复制到第二类以避免超级调用(类链可能很长)
  • 递归调用应该由最时髦的类
  • 完成

我正在尝试一些想法,但它们似乎很糟糕的编程实践或不可能:

// 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?
}

我也尝试过使用可选参数:

// 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:解释我想解决的真正问题(来自Joachim Pileborg)

我正在创建一个用户界面库,即一组类和函数,用于创建容易的小部件,如框架,按钮,输入文本等。

我创建了一个具有大多数常规功能的基本(根类)小部件,一个“可见”小部件,用于实现具有可见部分的小部件的所有通用功能,以及soo。

还有一些容器,如框架,布局和窗口。

现在遇到困难的部分:有一个函数“updateStyle”应该立即更新窗口小部件的所有图形部分(并重绘它):这个函数递归调用超类来执行更多通用功能,并且还必须递归调用容器来传播更改(小部件的尺寸和位置可能会发生变化)

enter image description here

每个小部件都应该“按此”工作,也可以扩展,这就是为什么这些要求。

代码很广泛(大约8k行)并且有很多其他功能,所以没有必要在这里复制 代码。

4 个答案:

答案 0 :(得分:2)

看起来您正在搜索模板方法模式

  • 在基类中,实现一个非虚方法,概述函数的一般行为。
  • 定义(抽象)定义该函数内特殊行为部分的虚拟方法
  • 在派生类中,覆盖特殊行为

    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只更新单个对象。

答案 1 :(得分:1)

根据Herb Sutter's article: Virtuality,我们应该更喜欢将虚拟功能设为私有。这意味着我们应该尽量避免调用超级"版本,而是让基类做的工作。

以下是一个例子:

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

答案 2 :(得分:0)

对于第一个例子,

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

ExtNode::getNumValues()计算树的另一个函数。

的结果

ExtNode::getNumValues()( Node::getNumValues(), tree )的函数,或者仅取决于树。

对于UI问题, 想想责任链设计模式。将更新请求转发到根节点,根节点又启动树遍历以从root开始更新所有节点。

答案 3 :(得分:-1)

解决这个问题的一种方法是使函数非虚拟,然后在每个覆盖中显式调用super classe函数(类似于构造函数)。

使方法非虚拟意味着每个继承的类都将拥有它自己的方法实现,因此您不会通过编写实现来覆盖classe的父代码。功能。

缺点是您必须通过明确指定某种类型的指针调用该函数,从而迫使您知道该类型。

要避免这个缺点,请创建一个调用所需递归函数的虚函数,然后使用该函数。

作为旁注,应避免使用非虚函数。

这是一个示例代码

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

}

对于上面的代码,输出将是"名为ext的名为ext的base称为ext2,名为"。

如果在基类中声明doStuff函数是虚拟的(从而使其对每个子类都是虚拟的),那么输出将是"名为ext2的名为ext2的名为base的名为ext的名为ext2的名为"。