mora mora - 1 month ago 9
C++ Question

Why does 'visitor pattern' asks each class to inherit VisitorHost interface class which has accept() function?

I am learning a design pattern, vistor pattern, in c++.

At first, I copy my two practice codes below. First one is "my test code", and the second one is a simplified code of "normal visitor pattern" in my text book. I would like you to read the first code but the second one is just a reference code of normal visitor pattern.

My question is why visitor pattern asks each class to inherit VisitorsHostInterface class which has virtual function, accept(); please refer to the second code, "normal visitor pattern", below if necessary. In my understanding, it is not necessary to use accept() function to scan all instances, like the first code, "my test code".
I suppose "my test code" is simpler than "normal visitor pattern".
Please tell me the reason why visitor pattern asks accept() function to each class? Thank you very much.

(1) my test code

class ClassD;

class ClassC {
public:
ClassC(int new_value, ClassD* new_next_object) : value_(new_value), next_object_(new_next_object) {};
void print() { std::cout << "ClassC value_ = " << value_ << std::endl; }
int value() { return value_; }
std::shared_ptr<ClassD> next_object(void) { return next_object_; }
private:
int value_=0;
std::shared_ptr<ClassD> next_object_;
};

class ClassD {
public:
ClassD(int new_value, ClassC* new_next_object) : value_(new_value), next_object_(new_next_object) {};
void print() { std::cout << "ClassD value_ = " << value_ << std::endl; }
int value() { return value_; }
std::shared_ptr<ClassC> next_object(void) { return next_object_; }
private:
int value_=0;
std::shared_ptr<ClassC> next_object_;
};

class VisitorFuncInterface {
public:
virtual ~VisitorFuncInterface() = default;
virtual void visit(ClassC* obj) = 0;
virtual void visit(ClassD* obj) = 0;
};

class VisitorFunc : public VisitorFuncInterface {
public:
virtual ~VisitorFunc() = default;
void visit(ClassC* obj) {
if (obj) {
obj->print();
this->visit(obj->next_object().get());
}
}
void visit(ClassD* obj) {
if (obj) {
obj->print();
this->visit(obj->next_object().get());
}
}
};

void test_visitor_without_host(void) {

ClassD* d0 = new ClassD(0, nullptr);
ClassC* c0 = new ClassC(1, d0);
ClassD* d1 = new ClassD(2, c0);

VisitorFunc v;
v.visit(d1);

delete d1;

}


The result of test_visitor_without_host() is following,

ClassD value_ = 2
ClassC value_ = 1
ClassD value_ = 0


(2) normal visitor pattern code

class ClassA;
class ClassB;

class VisitorInterface {
public:
virtual ~VisitorInterface() = default;
virtual void visit(ClassA* obj) = 0;
virtual void visit(ClassB* obj) = 0;
};

class VisitorsHostInterface { // = visitor's host
public:
virtual ~VisitorsHostInterface() = default;
virtual void accept(VisitorInterface& v) = 0;
};

class VisitorsHost : public VisitorsHostInterface {
public:
virtual ~VisitorsHost();
void accept(VisitorInterface& v) {};
};

class ClassA : public VisitorsHostInterface {
public:
ClassA(int new_value, ClassB* new_next_object) : value_(new_value), next_object_(new_next_object) {};
void print() { std::cout << "ClassA value_ = " << value_ << std::endl; }
int value() { return value_; }
std::shared_ptr<ClassB> next_object(void) { return next_object_; }
void accept(VisitorInterface& v) { v.visit(this); };
private:
int value_=0;
std::shared_ptr<ClassB> next_object_;
};

class ClassB : public VisitorsHostInterface {
public:
ClassB(int new_value, ClassA* new_next_object) : value_(new_value), next_object_(new_next_object) {};
void print() { std::cout << "ClassB value_ = " << value_ << std::endl; }
int value() { return value_; }
std::shared_ptr<ClassA> next_object(void) { return next_object_; }
void accept(VisitorInterface& v) { v.visit(this); };
private:
int value_=0;
std::shared_ptr<ClassA> next_object_;
};

class Visitor : public VisitorInterface {
public:
virtual ~Visitor() = default;
void visit(ClassA* obj) {
if (obj) {
obj->print();
this->visit(obj->next_object().get());
}
}
void visit(ClassB* obj) {
if (obj) {
obj->print();
this->visit(obj->next_object().get());
}
}
};

void test_visitor(void) {

ClassB* b0 = new ClassB(0, nullptr);
ClassA* a0 = new ClassA(1, b0);
ClassB* b1 = new ClassB(2, a0);

Visitor v;

b1->accept(v);

delete b1;

}


The result of test_visitor() is following,

ClassB value_ = 2
ClassA value_ = 1
ClassB value_ = 0

Answer

In your example, you hold all the objects by value and know their static & dynamic type. You don't need dynamic dispatch, so you don't need to have a common VisitorsHostInterface base class. All that's required is for your classes to implement an accept function.

However, the visitor pattern is most commonly used when you don't have access to the dynamic type of the visited objects. Say you have a vecotr<unique_ptr<Widget>>. Where many different Widget sub-types are stored by pointer. Than Widget and every one of its sub classes must implement a virtual accept function. To get to the objects dynamic type, you need to do dynamic dispatch.

Comments