Посетитель и двойная отправка без переопределения метода accept в С++

Хорошо: вот моя проблема: у меня есть базовый составной класс, который принимает посетителя, а затем перебирает его узлы. Работает как шарм. Но затем я должен использовать производный от этого композита и признать, что мне нужно переопределить метод «accept()» в производном классе, чтобы иметь правильную двойную диспетчеризацию (чего я не понял).

Это выявляет два недостатка: во-первых, мне приходится ломать скрытую структуру базы, а во-вторых, мне приходится дублировать код. Чтобы прояснить, вот мой псевдокод:

struct Visitor
{
    void visit( BaseComposit*)    { throw( "not expected"); };
    void visit( DerivedComposit*) { throw( "ok"); };
};

class BaseComposit 
{
private:  

    std::vector< BaseComposit*> nodes;

public:

    virtual void accept( Visitor* v)
    {
        v->visit( this);

        for( int i = 0; i < nodes.size(); i++)
            nodes[ i]->accept( v);
    }
};

class DerivedComposit : public BaseComposit
{
public:
};

Любое элегантное решение по этому поводу? Спасибо !

Изменить: добавлено «виртуальное» в «принять ()», чтобы сделать его более точным...


person affenärschle    schedule 07.12.2017    source источник
comment
Придирка, но пробел после открывающей скобки, который не соответствует закрывающей скобке, немного раздражает.   -  person StoryTeller - Unslander Monica    schedule 07.12.2017
comment
Почему бы вам не объявить accept как виртуальный в базовом классе и не реализовать переопределение в производном? шаблон посетителя именно таков.   -  person Oliv    schedule 07.12.2017
comment
Вы можете попробовать ациклический посетитель   -  person n. 1.8e9-where's-my-share m.    schedule 07.12.2017
comment
@StoryTeller: извините. не вижу тут твоей мысли...   -  person affenärschle    schedule 07.12.2017
comment
Связанное чтение: stroustrup.com/multimethods.pdf Stroustrup уже придумал, как сделать мультидиспетчерскую языковая функция   -  person king_nak    schedule 07.12.2017
comment
Мой ( point)( is)( that)( this) стиль может вызвать много дискуссий при ревью кода. Поверьте мне, одно пространство может выделяться не меньше, чем многие другие.   -  person StoryTeller - Unslander Monica    schedule 07.12.2017
comment
@StoryTeller: после 35 лет программирования это больше не изменит мой мозг ;-) То же, что Type* type vs. Type /*type vs. Type/* pType vs. и так далее....   -  person affenärschle    schedule 07.12.2017
comment
Я могу получить ( this ) или (this), но боюсь, что ( this) всегда будет проблемой для меня при проверке кода. Но YMMV :)   -  person StoryTeller - Unslander Monica    schedule 07.12.2017
comment
sry - первый раз пользователь: как я могу поставить звездочку в тексте? Ааа - сам обнаружил, что мне нужно поставить обратную косую черту :-(   -  person affenärschle    schedule 07.12.2017
comment
Я понял тебя, не волнуйся. Если вы хотите узнать о возможностях разметки SO, в том числе в комментариях, для этого есть страница справки.   -  person StoryTeller - Unslander Monica    schedule 07.12.2017


Ответы (2)


Любое элегантное решение по этому поводу?

Не совсем. Вот что делает шаблон посетителя немного болезненным. Хотя вы можете немного уменьшить дублирование с помощью шаблона:

class BaseComposit 
{
private:  

    std::vector<BaseComposit*> nodes;

protected:

    template<class T>
    void accept_impl( Visitor* v, T* this_ )
    {
        v->visit( this_ );

        for(int i = 0; i < nodes.size(); i++)
            nodes[i]->accept(v);
    }

public:

    virtual void accept( Visitor* v ) { accept_impl( v, this ); }
};

Теперь дублирование, которое должен выполнить accept, меньше.

Кроме того, как указал @Oliv, в вашем примере действительно должна быть accept виртуальная функция. В противном случае все это не будет работать.


Если вы чувствуете себя действительно предприимчивым, вы можете ввести макрос, чтобы упростить «введение» accept в каждый класс. Вот так:

#define INJECT_ACCEPT() void accept( Visitor* v ) override { accept_impl( v, this ); }

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

class DerivedComposit : public BaseComposit
{
public:
    INJECT_ACCEPT();
};

Точка с запятой разрешена любопытной особенностью грамматики C++. Поэтому я полагаю, что можно утверждать, что вышеизложенное выглядит «естественно».

person StoryTeller - Unslander Monica    schedule 07.12.2017
comment
Но это всегда BaseComposit*? - person Oliv; 07.12.2017
comment
@Oliv - Другое дело сделать его виртуальным :) - person StoryTeller - Unslander Monica; 07.12.2017
comment
Спасибо - я пойду с шаблоном, и да, конечно, он должен быть виртуальным. Вот почему я написал псевдокод ;-) Еще раз спасибо за быстрый резонанс! Отличное сообщество! - person affenärschle; 07.12.2017
comment
@user9066185 user9066185 - Ну, я добавил еще один лакомый кусочек. Это позволяет избежать повторения, но это немного сомнительно. - person StoryTeller - Unslander Monica; 07.12.2017
comment
Какая польза от метода шаблона здесь? Это просто для того, чтобы сделать код более C++y? - person king_nak; 07.12.2017
comment
@king_nak - Нет, это нужно, чтобы не писать все тело функции снова и снова только потому, что мы хотим, чтобы статический тип this каждый раз был другим. Вы можете называть это C++y, я называю это D.R.Y. - person StoryTeller - Unslander Monica; 07.12.2017
comment
Вы можете добиться этого и другими способами. Просто разделите алгоритм (перебор узлов) и действие (что делает каждый узел). Используя шаблон шаблонного метода. Например. вот так ideone.com/u9tEzy - person king_nak; 07.12.2017
comment
@king_nak - Да, NVI тоже достойная альтернатива. И это тоже было случайно предложено. Что использовать, и это может даже включать смесь, зависит от исполнителя, исходя из его потребностей. - person StoryTeller - Unslander Monica; 07.12.2017
comment
А, только что увидел другой ответ. Не запомнил термин NVI. И с вашим решением: мне часто трудно интерпретировать методы шаблонов, которые обрабатывают иерархию, поэтому я стараюсь их избегать;) - person king_nak; 07.12.2017
comment
@king_nak - полагаю, это твоя прерогатива :) - person StoryTeller - Unslander Monica; 07.12.2017

Если вы хотите, чтобы какой-то код всегда выполнялся, используйте шаблон невиртуального интерфейса (NVI):

class BaseComposit 
{
private:  
    std::vector<BaseComposit*> nodes;

    virtual void call_visit(Visitor* v) { v->visit(this); }

public:

    void accept(Visitor* v)
    {
        call_visit(v);

        for(int i = 0; i < nodes.size(); i++)
            nodes[i]->accept(v);
    }
};

class DerivedComposit : public BaseComposit
{
    void call_visit(Visitor* v) override { v->visit(this); }
public:
};
person Sebastian Redl    schedule 07.12.2017
comment
@affenärschle: так что избавься от override. - person Jarod42; 07.12.2017
comment
@ Jarod42: jup - работает. Но я не понимаю, почему компилятор может выбрать правильную запись vtable в Base/Derived, а не в посетителе... В любом случае, Себастьян, хорошее решение. Ок - у посетителя нет втабеля... позор мне - person affenärschle; 07.12.2017