C++: приведение производного указателя к указателю void, а затем к абстрактному указателю и доступ к функциям-членам

У меня есть функция контроллера, которая заботится о ряде объектов, все конкретные классы которых являются производными от чистого виртуального класса.

class Abstract {
public:
    virtual bool ReadyForWork() = 0;
    virtual void DoWork() = 0;
};

Тогда у меня есть следующее:

class Specific : public virtual Abstract {
public:
    bool ReadyForWork();
    void DoWork();
};

Программа создает экземпляр Specific и присваивает его пустому указателю в цепочке.

vChain->Append(new Specific());

Все идет нормально. Когда функция контроллера срабатывает и пытается получить доступ к конкретной реализации виртуальных функций Abstract, хотя... фу.

Конкретный объект есть, я могу получить доступ к его содержимому, приведя внутреннюю пустоту* vChain к Specific*, и все это проверяется, так что плохая память не является проблемой. Но в тот момент, когда я привожу void* к Abstract*, компилятор теряет ссылки на реализации Specific, изменяя адреса функций на что-то совершенно отличное от того, что было раньше, даже несмотря на то, что указатель на сам объект правильный. Пытающийся

// ...
Abstract *vWorkObj = (Abstract*)vChain->GetObj();
    if (vWorkObj->ReadyForWork()) {
// ...

приводит к следующему в моем лице:

Unhandled exception at 0x00000054 in Proj.exe:
0xC0000005: Access violation executing location 0x00000054.

Но если я сделаю это вместо этого

// ...
Abstract *vWorkObj = (Specific*)vChain->GetObj();
    if (vWorkObj->ReadyForWork()) {
// ...

он работает без сбоев. К сожалению, это мне мало помогает, так как будет несколько классов, наследуемых от Abstract.

Я пробовал что-то подобное в проекте проверки концепции, и все прошло гладко:

// ...
bool ReadyForWork(Abstract *pObjPtr) {
// ...

// ...
Specific *vObj = new Specific();
if (ReadyForWork(vObj)) {
// ...

По-видимому, компилятору не нравится разрешать наследование классов во время выполнения. Есть ли способ получить доступ к наследнику абстрактного класса без явного указания компилятору, какой это наследник?

Я подозреваю, что приведение к void* и обратно может быть причиной того, что компилятор теряет ориентацию, но это избавило бы от некоторых хлопот, если бы цепочка могла быть максимально универсальной.

Я использую VS2013 Express для рабочего стола Windows. Спасибо за ваше время.


person Platedslicer    schedule 27.01.2014    source источник
comment
Попробуйте без виртуального наследования. Какой тип аргумента для vChain-›Append()?   -  person user207421    schedule 27.01.2014
comment
@EJP: дох! Отнятие виртуального наследства исправило это. Спасибо. Append() принимает аргумент void*.   -  person Platedslicer    schedule 27.01.2014


Ответы (2)


Прямой источник проблемы, вероятно, в том, что вы используете виртуальное наследование, что усложняет компоновку вашего класса. Обычно это требуется только в сложных сценариях множественного наследования, поэтому, возможно, вам это не понадобится.

Вам нужен указатель на подобъект Abstract объекта Specific, который в вашем случае может не совпадать с адресом памяти. Поэтому, если вы сохраняете Abstract* в vChain, убедитесь, что вы сохранили правильный адрес, сначала преобразовав его в Abstract*:

vChain->Append(static_cast<Abstract*>(new Specific()));

Кроме того, возникает вопрос, почему vChain содержит void* в первую очередь. Если бы он хранил Abstract*, не было бы необходимости в явных приведениях, и компилятор автоматически определял бы правильные указатели.

person sth    schedule 27.01.2014
comment
Проблема заключалась в виртуальном наследовании. Что касается Цепи, принимающей пустоту*, то это просто потому, что она используется во всей программе для организации всевозможных вещей, а не только Абстрактов. Было бы лучше, если бы у меня была цепочка для каждого типа вместо того, чтобы разбрасывать приведения повсюду, но это также лишало бы смысла иметь общую цепочку в первую очередь. - person Platedslicer; 27.01.2014
comment
Звучит так, будто Цепь должна быть шаблоном, чтобы у вас могли быть Chain<Abstract*> и Chain<Foo> и так далее. Вероятно, в стандартной библиотеке также есть структура данных, которая делает то, что вы хотите, например std::vector<Abstract*>. - person sth; 27.01.2014
comment
В этом есть смысл. Chain делает некоторые вещи, очень специфичные для этой конкретной программы, поэтому я не уверен в замене его вектором... Я еще не пробовал свои силы в шаблонах, похоже, сейчас самое подходящее время для начала. Спасибо. - person Platedslicer; 27.01.2014

Как правило, при преобразовании foo* в void* безопасно только преобразовать обратно в точно такой же тип. Есть случаи, когда это работает, есть случаи, когда это работает, но в основном в каждом случае вам лучше следовать этому правилу.

Если вы собираетесь позже преобразовать void* в abstract*, преобразуйте foo* в abstract* перед преобразованием в void*. Лучший вид такого преобразования — неявный.

abstract* p = new foo;
void* v = static_cast<void*>(p);
abstract*p2 = static_cast<abstract*>(v);

наконец, если вы знаете, что собираетесь преобразовать в abstract* позже, рассмотрите возможность изменения типа указателя, который вы тем временем храните, на abstract*.

person Yakk - Adam Nevraumont    schedule 27.01.2014
comment
Хорошо, я буду помнить об этом! Согласно комментарию sth в его ответе выше, кажется, что безопаснее было бы превратить Chain в шаблон. - person Platedslicer; 27.01.2014