Есть два хороших примера. Я знаю, где посетитель явно предпочтительнее итератора.
Первый взаимодействует с некоторым неизвестным набором членов класса, в частности в C++. Например, вот посетитель, который печатает все члены других классов. Представьте, что вы являетесь автором Printer
, а кто-то, с кем вы не знакомы, является автором Heterogeneous3Tuple
.
#include <iostream>
template<class ElemType1, class ElemType2, class ElemType3>
class Heterogeneous3Tuple
{
public:
Heterogeneous3Tuple(ElemType1 elem1, ElemType2 elem2, ElemType3 elem3)
: elem1_(std::move(elem1)), elem2_(std::move(elem2)), elem3_(std::move(elem3))
{}
template<class Visitor>
void accept(const Visitor& visitor)
{
visitor(elem1_);
visitor(elem2_);
visitor(elem3_);
}
private:
ElemType1 elem1_;
ElemType2 elem2_;
ElemType3 elem3_;
};
class Printer
{
public:
template<class VisitedElemType>
void operator()(const VisitedElemType& visitee) const
{
std::cout << visitee << std::endl;
}
private:
};
int main() {
Heterogeneous3Tuple<char, int, double> h3t('a', 0, 3.14);
Printer p;
h3t.accept(p);
}
a
0
3.14
coliru
Там нет разумного способа, чтобы получить итератор, чтобы работать здесь. Даже не зная, какие типы могут взаимодействовать с классом Printer
, это работает, пока посетитель accept()
ed, и все элементы взаимодействуют аналогично с operator <<
и потоком.
Другой хороший пример, который я знаю о проявлениях в абстрактных синтаксических манипуляциях с деревьями. CPython и LLVM используют посетителей. Использование посетителя здесь предотвращает использование кода, который управляет определенными узлами AST, из-за необходимости знать, как выполнять итерацию по всем различным узлам AST, которые могут ветвиться сложными способами. LLVM source code подробнее. Вот изюминка:
/// Instruction visitors are used when you want to perform different actions
/// for different kinds of instructions without having to use lots of casts
/// and a big switch statement (in your code, that is).
///
/// To define your own visitor, inherit from this class, specifying your
/// new type for the 'SubClass' template parameter, and "override" visitXXX
/// functions in your class. I say "override" because this class is defined
/// in terms of statically resolved overloading, not virtual functions.
///
/// For example, here is a visitor that counts the number of malloc
/// instructions processed:
///
/// /// Declare the class. Note that we derive from InstVisitor instantiated
/// /// with _our new subclasses_ type.
/// ///
/// struct CountAllocaVisitor : public InstVisitor<CountAllocaVisitor> {
/// unsigned Count;
/// CountAllocaVisitor() : Count(0) {}
///
/// void visitAllocaInst(AllocaInst &AI) { ++Count; }
/// };
///
/// And this class would be used like this:
/// CountAllocaVisitor CAV;
/// CAV.visit(function);
/// NumAllocas = CAV.Count;
Извините, но я не согласен с вашим ответом. Оба шаблона Visitor и Iterator могут использоваться для «посещения» структуры объекта. Как вы правильно сказали, весь посещаемый элемент в шаблоне посетителя реализует «супер-интерфейс». Как следствие (как хорошо сказано в книге), нет необходимости, чтобы посещаемый объект имел общий класс (но общий интерфейс). Я не уверен, это пример, показанный в тексте: почему бы не вернуть объект, который реализует интерфейс вместо объекта класса? В более простых словах: если общий интерфейс «IVisitable», напишите 'IVisitable CurrentItem()' – justHelloWorld
Вы можете использовать итератор вместо списка, чтобы перебирать элементы, а затем использовать посетителя. Посетитель использует данные объектов, которые он посещает, для выполнения некоторых вычислений или выполнения некоторых других действий с данными. Итератор просто перемещается по коллекции/агрегату, скрывая его реализацию. Но вы действительно можете реализовать Iterator, который работает на интерфейсе. – BoeseB