Doees the 'this' pointer participate in a virtual function's polymorphic behavior

Viewed 53

I'm implementing the Visitor pattern, and I have an accept() function that is only defined in the base visitable class. This system works just fine, but Alexandresku states in his book that the this pointer has static typing.

Is this code flawed and/or has a bug, but my output is not catching it?

I have tried to change references to pointers, but the output does not change.

.h file

class WidgetVisitor
{
public:
    virtual void visit(ConcreteWidget1& w1);
    virtual void visit(ConcreteWidget2& w2);
    virtual void visit(Widget& w);
    virtual ~WidgetVisitor() { }
};


class Widget
{
public:
    Widget(const std::string& text);
    virtual std::string text() const;
    virtual void accept(WidgetVisitor* v);
protected:
    std::string text_;
};


class ConcreteWidget1: public Widget
{
public:
    ConcreteWidget1(const std::string& text);
    virtual std::string text() const override;

};

class ConcreteWidget2: public Widget
{
public:
    ConcreteWidget2(const std::string& text);
    virtual std::string text() const override;
};

.cpp file

void WidgetVisitor::visit(ConcreteWidget1& w1) {
    cout << w1.text() << '\n';
}

void WidgetVisitor::visit(ConcreteWidget2& w2) {
    cout << w2.text() << '\n';
}

void WidgetVisitor::visit(Widget& w) {
    cout << w.text() << '\n';
}


Widget::Widget(const std::string& text): text_(text) { }

string Widget::text() const {
    return "Widget" + text_;
}

void Widget::accept(WidgetVisitor* v) {
    v->visit(*this);
    cout << "in the class " << text_ << '\n';
}

ConcreteWidget1::ConcreteWidget1(const string& text): Widget(text) { }

string ConcreteWidget1::text() const {
    return "ConcreteWidget1" + text_;
}

ConcreteWidget2::ConcreteWidget2(const string& text): Widget(text) { }

string ConcreteWidget2::text() const {
    return "ConcreteWidget2" + text_;
}

main.cpp

int main() {

    WidgetVisitor* visitor = new WidgetVisitor();
    std::vector<Widget*> v;
    v.push_back(new Widget(" 1"));
    v.push_back(new ConcreteWidget1(" 2"));
    v.push_back(new ConcreteWidget2(" 3"));
    v.push_back(new ConcreteWidget1(" 4"));

    for (auto & w: v)
        w->accept(visitor);

    return 0;
}

Output is:

Widget 1
in the class  1
ConcreteWidget1 2
in the class  2
ConcreteWidget2 3
in the class  3
ConcreteWidget1 4
in the class  4
1 Answers

This isn't actually working like you think it's working. The only visitor being called is void WidgetVisitor::visit(Widget& w) but since the Widget::text() method is virtual, all of the visitor methods effectively have the same behavior.

Change the implementations to output the name of the reference argument's type and you'll see:

void WidgetVisitor::visit(ConcreteWidget1& w1) {
    cout << "[cw1] " << w1.text() << '\n';
}

void WidgetVisitor::visit(ConcreteWidget2& w2) {
    cout << "[cw2] " << w2.text() << '\n';
}

void WidgetVisitor::visit(Widget& w) {
    cout << "[w] " << w.text() << '\n';
}

You get this output:

[w] Widget 1
in the class  1
[w] ConcreteWidget1 2
in the class  2
[w] ConcreteWidget2 3
in the class  3
[w] ConcreteWidget1 4
in the class  4

So as you can see, the other two visitor overloads are simply not being used.

this is indeed statically typed; if it points to a polymorphic type then virtual methods called on it are still dispatched using dynamic dispatch, but Widget::accept() isn't actually invoking anything on this, it's just taking a reference to it. When you pass this reference somewhere else it will still be treated as its static type, meaning that overload resolution is still going to pick the Widget& overload here, because this is always Widget*:

v->visit(*this);

This can work but you have to override Widget::accept() in derived classes. For example:

class ConcreteWidget1: public Widget
{
public:
    ConcreteWidget1(const std::string& text);
    virtual std::string text() const override;
    virtual void accept(WidgetVisitor* v) override;
};

void ConcreteWidget1::accept(WidgetVisitor* v) {
    v->visit(*this);
    cout << "in the class " << text_ << '\n';
}

Do the same for ConcreteWidget2 and now you'll notice that the right visitor overload is called:

[w] Widget 1
in the class  1
[cw1] ConcreteWidget1 2
in the class  2
[cw2] ConcreteWidget2 3
in the class  3
[cw1] ConcreteWidget1 4
in the class  4

Now you'd be able to make Widget::text() non-virtual and see exactly the same output.

Related