Как бороться с "супер" вызовами и рекурсией

Мой вопрос о слиянии двух методов:

  • Вызов рекурсивно для суперфункций
  • Вызовите рекурсивно ту же функцию

Предположим, что корневой класс имеет рекурсивную функцию (foo) и расширенный класс, который переопределяет эту функцию (foo): функция переопределения должна вызывать super:: foo, но для выполнения рекурсивных операций требуется выполнить другие операции.

Я попробую пример (это всего лишь пример, и я знаю, что существует нерекурсивный способ решить эту проблему)

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.
  • Я бы не стал копировать код из метода первого класса во второй, чтобы избежать вызова Super (цепочка классов может быть длинной)
  • Рекурсивный вызов, вероятно, должен выполняться младшим классом

Я пробую некоторые идеи, но они кажутся плохой практикой программирования или не возможны:

// 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: Объяснение реальной проблемы, которую я пытаюсь решить (спросил Иоахим Пилеборг)

Я создаю библиотеку пользовательского интерфейса, то есть набор классов и функций для создания легко виджетов, таких как кадр, кнопки, входные тексты и т.д.

Я создал базовый (корневой класс) виджет с большинством общих функций, виджет "Видимый" для реализации всех общих функций для виджетов, которые имеют видимую часть, и soo on.

Существуют также некоторые контейнеры, такие как фреймы, макет и окна.

Теперь пришла трудная часть: есть функция "updateStyle", которая должна сразу обновлять всю графическую часть виджета (и перерисовывать ее): эта функция рекурсивно вызывает суперкласс для выполнения более общих функций и также необходимо вызывать рекурсию в контейнеры для распространения изменений (размеры и позиции виджетов могут меняться)

enter image description here

Каждый виджет должен работать "как это", а также быть расширяемым, поэтому эти требования.

Код обширен (около 8 тыс. строк) и имеет множество других функций, поэтому не нужно копировать здесь код.

Ответ 1

Похоже, вы ищете шаблон метода шаблона:

  • в базовом классе реализует невиртуальный метод, который описывает общее поведение функции.
  • определить (абстрактные) виртуальные методы, которые определяют специальные части поведения внутри этой функции
  • в производных классах, переопределите специальное поведение

    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, который выполняет обновление только одного объекта.

Ответ 2

Согласно статья Herb Sutter: 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;}
};

Ответ 3

В первом примере

Node::getNumValues() вычисляет некоторую функцию дерева.

ExtNode::getNumValues() вычисляет другую функцию дерева. Либо результат

ExtNode::getNumValues() является функцией ( Node::getNumValues(), tree ) или зависит только от дерева.

Для проблемы пользовательского интерфейса, Подумайте о схеме дизайна ответственности. Переслать запрос на обновление до корня node, который, в свою очередь, инициирует обход дерева для обновления всех узлов, начиная с root.

Ответ 4

Один из способов решения этой проблемы - сделать функцию не виртуальной, а затем явно вызвать в каждом переопределении функцию суперкласса (аналогично конструктору).

Наличие метода не виртуального означает, что каждый унаследованный класс будет иметь собственную реализацию метода, поэтому вы не будете перезаписывать родительский код 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, называемым ext2, называемым".

Если вы объявляете функцию doStuff виртуальной в базовом классе (тем самым делая ее virutal для каждого дочернего класса), то выход будет "base", называемый ext, называемый ext2, называемый базой, называемый ext с именем ext2, называемым ".