LINUX.ORG.RU

[C++] Бинарная совместимость


0

2

Интересует совместимость предков и наследников.

class T{
 public:
 virtual void foo()=0;};

class Child:public T{
 public:
 void foo(){
  printf("foo\n");}};

int main(){
 void *child=new Child;
 /*
 указатель гуляет по всем кругам ада; следующий код, вообще говоря,
 будет выполняться в другой функции, где компилятор точно не знает
 о происхождении void *child.
 */
 T *t=reinterpret_cast<T*>(child);
 t->foo();}

Есть ли гарантия, что во время t->foo() все пройдет гладко? Есть ли такая гарантия хотя бы для всех платформ гцц?

★★★★★

Ответ на: комментарий от staseg

>Указатель сильно гулящий. Информацию о типе не сохранить.

Тебе видать нужен делегат чтобы внешний код мог тебя дергать в нужный момент.

Absurd ★★★ ()

>Есть ли гарантия, что во время t->foo() все пройдет гладко? Есть ли такая гарантия хотя бы для всех платформ гцц?

Такой гарантии нет.

Но вот такое вот, вроде, гарантированно завершится успешно:

[code] void * child = (void*)(T*)(new Child());

((T*)child)->foo(); [/code]

dmitry_vk ★★★ ()
Ответ на: комментарий от Absurd

>Тебе видать нужен делегат чтобы внешний код мог тебя дергать в нужный момент.

Есть библиотека А на с++. Есть программа Пэ на си, которой эта библиотека нужна: создается плюсовая, extern-«C»-обернутая библиотека Б, слинкованная с А. В ней в том числе создаются объекты, которые «отправляются» в Пэ. В свою очередь Пэ переодически вызывает обертки из Б и передает им эти объекты. Так вот создавать хочется объекты Child, а в дальнейшем часть из них обрабатывать более обще, как T. Типов наследников много.

staseg ★★★★★ ()
Ответ на: комментарий от dmitry_vk

Кхм. Я не вижду разницы в нашем коде. И у меня, и у тебя указатель преобразуется «грязно» ((T*)child)->foo() через реинтерпрет.

staseg ★★★★★ ()
Ответ на: комментарий от staseg

Ну у меня есть древний прожект на ATL/WTL, там нужна была возможность вызывать методы плюсавых объектов (конкретно окон) не синхронно, а в контексте следующего сообщения через PostMessage. Там в цикл обработки сообщений можно передать только LPRARAM (32 бита, можно кастить к void*). Я тогда написал шаблон функции, который превращает два своих параметра - указатель объект и указатель на метод объекта - в инстанс интерфейса, чей единственный метод execute() дергает этот указатель на метод, подставляя в него сохраненный указатель на объект.

Этот инстанс интерфейса размещался в куче и указатель на него передавался в PostMessage через LPARAM. Ну и в цикле обработки сообщений был кусок, который отлаввивал WM_USER, получал этот интерфейс из LPARAM, дергал у него виртуальный метод execute(). После этого интерфейс убиваелся. Выглядело это примерно так:

Util::executeLater(this, &MyWindow::someMethod) // someMethod выполняется асинхронно

Absurd ★★★ ()
Ответ на: комментарий от Absurd

Понятно, что-то похожее. Если бы у меня была задача просто вызвать метод объекта по двум грязным указателям, я бы не парился. Но тут получается так, что сначала создается объект одного из типов Child1..N, а потом присходит работа с ним, как с T, причем в обработчике компилятору никак не уследить за тем, что было передано. Поэтому и встает вопрос о бинарной совместимости интерфейсов предка и наследника.

staseg ★★★★★ ()
Ответ на: комментарий от staseg

>Если бы у меня была задача просто вызвать метод объекта по двум грязным указателям, я бы не парился.

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

Absurd ★★★ ()
Ответ на: комментарий от pathfinder

>Я так и не понял, в чем проблема? Сишная программа ведь не изменяет объект на который ссылается указатель void*.

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

staseg ★★★★★ ()
Ответ на: комментарий от staseg

Проблема в том, что я опасаюсь, что потеряв информацию о типе, компилятор может неправильно подставить вызов метода.

Метод в предке ведь виртуальный. Чтобы вызвать виртуальный метод не надо знать никакой дополнительной информации о типе потомка.

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

pathfinder ★★★★ ()
Ответ на: комментарий от staseg

>>Я так и не понял, в чем проблема? Сишная программа ведь не изменяет объект на который ссылается указатель void*.

Проблема в том, что я опасаюсь, что потеряв информацию о типе, компилятор может неправильно подставить вызов метода.

Ну там вообще можно сделать хешмап вида ключ->объект. В сишный код передавать ключ.

Absurd ★★★ ()
Ответ на: комментарий от Absurd

Ну там вообще можно сделать хешмап вида ключ->объект. В сишный код передавать ключ.

Зачем? Чем тебе указатель не ключ?

pathfinder ★★★★ ()
Ответ на: комментарий от pathfinder

>Зачем?

Опасных преобразований типов делать не надо, например.

Absurd ★★★ ()
Ответ на: комментарий от pathfinder

>Метод в предке ведь виртуальный.

Точно. Что-то я запутался и не сообразил, что в t->foo() компилятор согласно типу (T) и имени функции (foo) однозначно доберется до T::foo, а потом по таблице виртуальных функций и до Child::foo.

Спасибо.

staseg ★★★★★ ()
Ответ на: комментарий от staseg

>Проблема в том, что я опасаюсь, что потеряв информацию о типе, компилятор может неправильно подставить вызов метода

Так и будет, например, при множественном наследовании. Именно предварительный (безопасный, т.е. static_cast, а не reinterpret) каст указателя к известному базовому классу позволяет гарантировать, что указатель останется валидным.

dmitry_vk ★★★ ()

Согласно великому и ужасному стандарту:

T *t1;
...
void *p = static_cast<void *>(t1);
T *t2 = static_cast<T *>(p);
assert(t1 == t2);

У тебя почти так, да не так. Смотри, что dmitry_vk написал выше.

const86 ★★★★★ ()
Ответ на: комментарий от const86

Смотри, что dmitry_vk написал выше.

Именно предварительный (безопасный, т.е. static_cast, а не reinterpret) каст указателя к известному базовому классу позволяет гарантировать, что указатель останется валидным.

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

void* create_object(){
 return static_cast<T*>(new Child);}

void process_object(void *t){
 (static_cast<T*>(t))->foo();}
staseg ★★★★★ ()
Ответ на: комментарий от staseg

правильно, но второй static_cast не обязателен

anonymous ()

А почему reinterpret_cast<> ? Вроде static_cast<> вполне подойдёт.

DELIRIUM ☆☆☆☆☆ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.