2013-03-23 4 views
4

Вопрос больше похож на теоретический.

Предисловие. посетителей картина:Перегрузка и этот указатель

class Visitor 
{ 
public: 
    virtual void VisitElementA(const ElementA& obj) = 0; 
    virtual void VisitElementB(const ElementB& obj) = 0; 
}; 

class Element 
{ 
public: 
    virtual void Accept(Visitor& visitor) = 0; 
}; 

class ElementA : public Element 
{ 
public: 
    void Accept(Visitor& visitor) override { visitor.VisitElementA(*this); } 
}; 

class ElementB : public Element 
{ 
public: 
    void Accept(Visitor& visitor) override { visitor.VisitElementB(*this); } 
}; 

Это VisitElementA (Const Elementa & объекта) выглядит немного некрасиво, поэтому использование перегрузки можно переписать так:

class Visitor 
{ 
public: 
    virtual void Visit(const ElementA& obj) = 0; 
    virtual void Visit(const ElementB& obj) = 0; 
}; 

И теперь у нас есть два одинаковые реализаций метода принятия в элементах ElementA и ElementB:

void Accept(Visitor& visitor) override { visitor.Visit(*this); } 

И такой код должен быть добавлен в ElementC, ElementD и т. Д. (Если есть)

Вопрос: Как избежать этого дублирования?

Наивное решение поместить Accept реализации внутри класса Element (или какой-либо другой промежуточного класса) не будет работать из-за этот указатель будет указывать на объект, чтобы объект класса Element, а не Elementa или ElementB, и, таким образом, в лучшем случае мы получим ошибку компиляции или даже неправильное поведение (если будет некоторый перегруженный метод Visit для Element).

Насколько я понимаю, проблема заключается в попытке сочетать функции компиляции и времени выполнения. Но может существовать какое-то решение на основе шаблонов или новая функция C++ 11 или что-то еще?

Одно замечание: я был бы признателен, если вы не предложите решение с «магией макросов» :).

ответ

2

Вы можете использовать шаблон CRTP.

Преобразование класса Element в класс шаблона, который принимает производный тип как параметр типа. Тогда вы можете обратное приведение к производному типу перед вызовом посетителя:

template <typename Derived> 
class Element 
{ 
public: 
    void Accept(Visitor& visitor) { visitor.Visit(*static_cast<Derived*>(this)); } 
}; 

Наконец, каждый конкретный элемент вытекает из Element таким образом:

class ElementA : public Element<ElementA> 
{ 
}; 

Обратите также внимание, что Accept(Visitor&) больше не должен быть виртуальным.

Update: Вот решение вопроса, что Кецалькоатль отметил:

class ElementC : public Element<ElementC>, public ElementA 
{ 
public: 
    using Element<ElementC>::Accept; 
}; 

Через использованием декларации ElementC приносит имя Accept в его объеме и, как следствие, те в базовые классы скрыты. Однако это Accept составляет Element<ElementC>::Accept, и на практике только ElementA::Accept скрыт.

+0

Да, об этом я тоже думал, когда заметил сбой смеси. Но CRTP не будет работать, если есть другое наследование, более глубокое, чем ElementA, т. Е. Когда у вас есть класс ElementC: ElementA. Вот почему я написал, что «смешивание должно знать тип FINAL» – quetzalcoatl

+1

Это определенно сработает, я использовал такой подход при реализации обобщенного шаблона Singleton (просто не знал, что он имеет имя) и (такой позор :)) забудь об этом. Благодарю. Но если вы не возражаете, я подожду еще некоторое время: может быть, кто-то знает лучшее решение. – cody

+0

Обратите внимание, что приведение не обязательно безопасно – newacct

0
class Visitor 
{ 
public: 
    virtual void Visit(const Element& obj) = 0; 
} 
class Element 
{ 
public: 
    void Accept(Visitor& visitor) { visitor.Visit(*this); } 
}; 

Редактировать 2: Вам необходимо вызвать метод Visit из базового класса элемента. Поскольку Элементы являются полиморфными, вы все равно передали правильный объект. Однако вам может потребоваться бросок, если вам нужно получить доступ к уникальным для элемента методам или ввести другие абстрактные методы.

+0

Точка шаблона Vistior предназначена для обработки разных объектов по-разному, поэтому я не могу просто реализовать Visit для некоторого базового класса, мне нужна другая реализация для A, B, C. Но все же Accept будет идентичным во всех этих классах. Что касается актера: если это нужно сделать на клиенте - это неприемлемо. – cody

+0

Я обновляю свой ответ, потому что, по-моему, я неправильно понял ваш вопрос. – Alex

+0

vash: Я удалил свой комментарий, потому что я неправильно читаю оригинальное сообщение. В случае, если вам удалось прочитать его тем временем: автор уже использует виртуальный-accept, поэтому он уже динамичен и не пытается получить полное статическое разрешение, поэтому мой комментарий был пропущен. – quetzalcoatl

Смежные вопросы