Полиморфизм с разыменованным указателем дает неожиданные результаты ... почему? - PullRequest
0 голосов
/ 01 ноября 2018

Я столкнулся с загадкой C ++ и мог бы помочь! Пожалуйста, рассмотрите следующий код:

#include <iostream>

struct Node
{
    virtual void print() 
    {
        std::cout << "Node::print" << std::endl;
    }
};

struct Drawable : public Node
{
    virtual void print() 
    {
        std::cout << "Drawable::print" << std::endl;
    }
};

struct Visitor
{
    virtual void apply(Node& node)
    {
        std::cout << "apply(Node&)" << std::endl;
        node.print();
    }
    virtual void apply(Drawable& drawable) 
    {
        std::cout << "apply(Drawable&)" << std::endl;
        drawable.print();
    }
};

struct Renderer
{
    virtual void accept(Node* node, Visitor* visitor)
    {
        visitor->apply(*node);
    }
};

int main(int argc, char** argv)
{
    Renderer r;
    Visitor v;
    Drawable* drawable = new Drawable();
    r.accept(drawable, &v);
    return 0;
}

Вывод:

apply(Node&)
Drawable::print

Я ожидал вызова Visitor :: apply (Drawable &), но вместо этого вызывается apply (Node &). Почему?

1 Ответ

0 голосов
/ 01 ноября 2018

Посмотрите на эту строку кода:

virtual void accept(Node* node, Visitor* visitor)
{
    visitor->apply(*node); // <--- Here
}

Когда компилятор видит этот вызов apply, он должен определить, хотите ли вы вызвать версию apply, которая принимает Node&, или версию apply, которая принимает Drawable& , Обратите внимание, что это решение о том, какую перегрузку выбрать, а какую переопределить . Решения о перегрузке принимаются во время компиляции. Здесь компилятор смотрит на выражение *node и говорит: «Ну, node - это Node *, поэтому *node - это Node&», потому что он не может знать во время компиляции, какой тип во время выполнения вещь, на которую указывает node, есть. В результате это всегда будет вызывать apply(Node &) и никогда не будет вызывать apply(Drawable&).

Это объясняет, что происходит, но как это исправить? Обычный способ настроить шаблон посетителя - поместить функцию, подобную этой, в основу иерархии классов:

struct Node {
    virtual void accept(Visitor* visitor);
};

Тогда бы каждый подкласс Node переопределил эту функцию Каждое переопределение будет выглядеть примерно так:

struct Pizkwat: Node {
    virtual void accept(Visitor* visitor) override {
        visitor->apply(*this);
        // do more things, optionally
    }
};

В строке кода в этом переопределении тип *this во время компиляции будет того же типа, что и рассматриваемый объект. Это означает, что выбор overload выберет версию apply, наиболее специфичную для этого типа.

Чтобы использовать эту accept функцию, вы можете написать что-то вроде этого:

virtual void accept(Node* node, Visitor* visitor)
{
    node->accept(visitor);
}

Теперь подумайте о том, что происходит. Функция-член accept помечена virtual, поэтому конкретная версия accept для вызова зависит от типа объекта, на который указывает node (то есть используется динамический тип а не статический тип ). При этом используется переопределение разрешение вместо перегрузка разрешение. Затем это вызовет правильное переопределение, которое, как вы видели выше, затем выберет правильную перегрузку .

Надеюсь, это поможет!

...