Передача производных экземпляров класса как void * универсальным обратным вызовам в C ++ - PullRequest
1 голос
/ 21 апреля 2010

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

У нас есть система обратного вызова, в которой модуль или приложение с одной стороны предоставляет «Сервис», и клиенты могут выполнять действия с этим Сервисом (в основном очень элементарный IPC). Для дальнейшего использования, скажем, у нас есть несколько определений, таких как:

typedef int (*callback)(void*); // This is NOT in our code, but makes explaining easier.

installCallback(string serviceName, callback cb); // Really handled by a proper management system

sendMessage(string serviceName, void* arg); // arg = value to pass to callback

Это прекрасно работает для базовых типов, таких как структуры или встроенные функции.

У нас есть структура MI, похожая на эту:

Device <- Disk <- MyDiskProvider

class Disk : public virtual Device
class MyDiskProvider : public Disk

Поставщик может быть любым, от драйвера оборудования, до клея, который обрабатывает образы дисков. Дело в том, что классы наследуют диск.

У нас есть «служба», которая должна быть уведомлена обо всех новых дисках в системе, и это то, что вещи распутывают:

void diskHandler(void *p)
{
    Disk *pDisk = reinterpret_cast<Disk*>(p); // Uh oh!

    // Remainder is not important
}

SomeDiskProvider::initialise()
{
    // Probe hardware, whatever...

    // Tell the disk system we're here!
    sendMessage("disk-handler", reinterpret_cast<void*>(this)); // Uh oh!
}

Проблема в том, что SomeDiskProvider наследует диск, но обработчик обратного вызова не может получить этот тип (так как указатель функции обратного вызова должен быть универсальным).

Могут ли здесь помочь RTTI и шаблоны?

Любые предложения будут с благодарностью.

Ответы [ 3 ]

1 голос
/ 21 апреля 2010

Ваш код выглядит рискованно. Проблема в том, что вы разыгрываете SomeDiskProvider * в void *, а затем возвращаете его в Disk *.

Вы должны вернуться к тому типу, из которого вы произвели. Таким образом, вы можете сначала разыграть SomeDiskProvider * до Disk *:

reinterpret_cast<void*>(static_cast<Disk *>(this))

или , которые вы отбрасываете назад к SomeDiskProvider *:

SomeDiskProvider *pDisk = reinterpret_cast<SomeDiskProvider*>(p);
1 голос
/ 22 апреля 2010

reinterpret_cast<> - это нормально, если вы конвертируете из some_type * в void * и обратно. Однако вы сказали, что используете множественное наследование, и в этом случае значение this в одном классе в вашей иерархии может не совпадать со значением this в другом классе; dynamic_cast<> предназначен для обхода дерева наследования и дает правильный ответ.

Итак, в SomeDiskProvider используйте оба приведения:

SomeDiskProvider::initialise()
{
    // Gets the right value for 'this' for the Disk part of SomeDiskProvider.
    Disk *pDisk = dynamic_cast<Disk *>(this);

    // OK, since the callback converts it back to Disk *
    sendMessage("disk-handler", reinterpret_cast<void*>(this));
}

Обратный вызов в точности соответствует тому, что вы показали в своем вопросе.

1 голос
/ 21 апреля 2010

Хотя я не уверен на 100%, я думаю, что static_cast от T * до void * и обратно к исходному указателю типа T * должен работать. На мой взгляд, проблема в том, что reinterpret_cast изменяет только тип указателя без изменения его фактического значения, а static_cast в случае наследования должен настроить указатель так, чтобы он указывал на начало объекта.

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Вы находитесь на темной стороне языка здесь, и я не могу гарантировать, что он будет работать вообще.

...