4 votes

Comportement étrange sans héritage virtuel

Je travaille sur un morceau de code qui présente un comportement très étrange. J'ai réussi à le reproduire dans un programme simple de style "hello world", voici le code :

#include <iostream>
using namespace std;

class Test
{
public:
    virtual ~Test() = default;

protected:
    virtual void SetUp() { }
};

class ICallbackReceiver
{
public:
    virtual ~ICallbackReceiver() = default;
    virtual void onReady() = 0;
};

// Callback de style C
void readyReceiver(void* userdata)
{
    cout << "3) readyReceiver\n";
    static_cast<ICallbackReceiver*>(userdata)->onReady();
}

using callback_t = void(*)(void*);
callback_t myCallback;
void* myUserData;

void registerCallback(callback_t callback, void* userData)
{
    cout << "2) registerCallback\n";
    myCallback = callback;
    myUserData = userData;
}

class ConfigurableTest : public /*virtual*/ Test, public ICallbackReceiver
{
public:

    void SetUp() override
    {
        cout << "1) ConfigurableTest::SetUp\n";
        registerCallback(&readyReceiver, static_cast(this));
    }

    void onReady() override
    {
        cout << "4) ConfigurableTest::onReady\n";
    }
};

int main()
{
    ConfigurableTest test;
    test.SetUp();

    myCallback(myUserData);

    return 0;
}

Chaque fois que myCallback est appelé, quelque chose doit être testé. Et voici la sortie qui devrait être affichée :

1) ConfigurableTest::SetUp
2) registerCallback
3) readyReceiver
4) ConfigurableTest::onReady

Mais, à moins que je spécifie un héritage virtuel pour la classe Test, voici la sortie que je vois :

1) ConfigurableTest::SetUp
2) registerCallback
3) readyReceiver
1) ConfigurableTest::SetUp
2) registerCallback

Comme vous pouvez le voir, ConfigurableTest::onReady n'est jamais appelé, mais ConfigurableTest::SetUp est en fait appelé deux fois !

Quelle est l'origine de ce comportement ? Comment puis-je refactoriser le code pour reproduire le comportement correct sans utiliser l'héritage virtuel ?

4voto

Anton Malyshev Points 6309

Le problème est que vous effectuez des conversions ConfigurableTest -> void * -> ICallbackReceiver, ce qui n'est pas permis. Vous devez convertir vers le même type ConfigurableTest revenir de void * - pas d'erreur dans ce cas.

Voir aussi héritage multiple: résultat inattendu après conversion de void * en 2e classe de base

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X