Виртуальный вызов из базы в виртуальном наследовании - PullRequest
1 голос
/ 18 октября 2019

У меня проблема с выполнением виртуального вызова при использовании виртуального наследования.

Ниже приведен пример компилируемого кода, который демонстрирует код, который работает, когда не используется виртуальное наследование, а также код, который не будет работать навремя выполнения при использовании виртуального наследования.

BASE CLASSES Ниже приведены базовые calsses для обоих случаев:

#include <iostream>

class Base
{
public:
    Base() { }
    virtual ~Base() { }

    // we need to make this bad call a good one!
    virtual void bad_call(void* ptr)
    {
        Base* pThis = static_cast<Base*>(ptr);
        pThis->f();
    }

protected:
    virtual void f() { std::cout << x << std::endl; }
    int x = 0;
};

class Midle1 :
    virtual public Base
{
public:
    Midle1() { }
    ~Midle1() override { }
};

class Midle2 :
    virtual public Base
{
public:
    Midle2() { }
    ~Midle2() override { }
};

CASE 1 GOOD Вотслучай, который не использует виртуальное наследование (просто обычное наследование), где работают как виртуальные функции bad_call, так и good_call:

class GoodDerived :
    public Base
{
public:
    GoodDerived()
    {
    }
    ~GoodDerived() override
    {
    }
    void good_call(void* ptr)
    {
        GoodDerived* pThis = static_cast<GoodDerived*>(ptr);
        pThis->f();
    }

    void f() override
    {
        ++x;
        std::cout << x << std::endl;
    }
};

int main()
{
    GoodDerived good_derived;
    good_derived.good_call(&good_derived);  // OK, will print 1
    good_derived.bad_call(&good_derived);   // OK, will print 2

    std::cin.get();
    return 0;
}

CASE 2 BAD И вот случайкоторый будет использовать виртуальное наследование, функция good_call будет выполнена успешно, но bad_call одна не удастся с «местом чтения нарушения доступа»

class BadDerived :
    public Midle1,
    public Midle2
{
public:
    BadDerived() { }
    ~BadDerived() override { }
    void good_call(void* ptr)
    {
        BadDerived* pThis = static_cast<BadDerived*>(ptr);
        pThis->f();
    }

    void f() override
    {
        ++x;
        std::cout << x << std::endl;
    }
};

int main()
{
    BadDerived bad_derived;
    bad_derived.good_call(&bad_derived); // OK, will print 1
    bad_derived.bad_call(&bad_derived); // ERROR: causes access violation

    std::cin.get();
    return 0;
}

ВОПРОС Этот второй случайпростой код, который продемонстрировал проблему, с которой я столкнулся прямо сейчас в своем проекте, и мне нужна помощь, чтобы решить, почему виртуальное наследование вызывает проблемы?

Почему первый случай работаетпросто отлично, а второй нет?

Ответы [ 4 ]

2 голосов
/ 19 октября 2019

Основная проблема заключается в том, что вы приводите указатель к void *, а затем приводите его к другому типу указателя. В общем, это не работает - после наведения указателя на void * единственная полезная вещь, которую вы можете сделать с ним, - привести его обратно к ТОЧНОМУ ЖЕ ТОЧНОМУ ТИПУ. Если вы хотите привести к любому другому типу указателя (надежно), вам нужно сначала привести к тому же самому исходному типу.

Мне нужна помощь, как решить эту проблему

Не используйте void * здесь - void * - это решение C, которое никогда не должно использоваться в C ++. Измените ваш виртуальный bad_call метод, чтобы он принимал Base * в качестве аргумента, а не void *. Тогда все «просто работает», и вам вообще не нужен ни один из static_cast. Если вам нужно переопределить bad_call в вашем классе Dervied, он также должен принять аргумент Base *, поэтому вам нужно будет использовать dynamic_cast<Derived *>(ptr) там, чтобы вернуть исходный Derived *, но это небольшое дело - это именно то, для чего существует dynamic_cast.

1 голос
/ 19 октября 2019

Давайте разберем это, шаг за шагом.

  1. &bad_derived: указатель на BadDerived со значением указателя, указывающим на объект типа BadDervived.
  2. bad_derived.bad_call(&bad_derived): неявно преобразует &bad_derived в указатель на void* со значением указателя, указывающим на объект типа BadDervived.
  3. Base* pThis = static_cast<Base*>(ptr);: приведение из void* в Base*. Обратите внимание, что ptr имеет значение указателя, указывающее на объект с типом BadDervived, но BadDerived не может быть преобразовано в указатель с Base, поэтому pThis имеет тип Base*, но имеет значение указателя, указывающее на объект стип BadDervived.
  4. pThis->f();: доступ к значению объекта BadDerived с использованием glvalue (здесь указатель с разыменовкой) типа Base нарушает правило строгого псевдонима. Неопределенное поведение.
1 голос
/ 18 октября 2019

Выполните вызов:

bad_derived.bad_call(static_cast<Base*>(&bad_derived));

Вы хотите указать на базовую часть объекта, но при использовании виртуального наследования нет никакой гарантии относительно того, где это будет находиться.

0 голосов
/ 19 октября 2019

Я хочу поделиться решением, которое делает возможным этот дизайн (с помощью других ответов и комментариев).

Весь код остается тем же, за исключением добавления шаблонного статического метода в базовый класс, который выведет void дляправильный тип:

Вот модифицированный базовый класс со статической функцией вспомогательного шаблона: также добавлен комментарий о CALLBACK.

class Base
{
public:
    Base() { }
    virtual ~Base() { }

    // this is example CALLBACK from Window API but templated
    // The callback is registered with Windows in RegisterClassEx btw.
    template<typename DERIVED_CLASS>
    static void make_call(void* ptr)
    {
        DERIVED_CLASS* pThis = static_cast<DERIVED_CLASS*>(ptr);
        pThis->bad_call(static_cast<Base*>(pThis));
    }

    // we need to make this bad call a good one!
    virtual void bad_call(void* ptr)
    {
        Base* pThis = static_cast<Base*>(ptr);
        pThis->f();
    }

protected:
    virtual void f() { std::cout << x << std::endl; }
    int x = 0;
};

А вот как мы вызываем bad_call проблемную функцию:

int main()
{
    BadDerived bad_derived;
    bad_derived.good_call(&bad_derived); // OK, will print 1

    // HACA!!!
    bad_derived.make_call<BadDerived>(&bad_derived);    // OK will print 2

    std::cin.get();
    return 0;
}

Вот почему я так люблю C ++, все возможно ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...