Как бороться с «супер» звонки и рекурсия

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

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

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

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

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

Есть также некоторые контейнеры, такие как рамки, макет и окна.

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

введите описание изображения здесь

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

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

10

Решение

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

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

    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

Другие решения

В соответствии с Статья Херба Саттера: Виртуальность, мы должны предпочесть сделать виртуальные функции приватными. Это подразумевает, что мы должны стараться избегать вызова «супер» версий и вместо этого заставить базовый класс работать.

Вот пример:

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

Для первого примера

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

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

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

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

0

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

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

Недостатком будет то, что вам придется вызывать функцию через указатель явно определенного типа, что заставит вас знать тип.

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

Как примечание, следует избегать не виртуальных функций.

Вот пример кода

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 с именем base с именем ext с именем ext2 named».

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

-1
По вопросам рекламы [email protected]