Вызов функции-члена нулевой структуры данных, который был приведен из несовместимого типа - Не определено? - PullRequest
0 голосов
/ 24 августа 2011

Существует прямая структура C, объявленная в неизменяемом заголовке.Я хотел бы «виртуально» добавить к нему удобные функции-члены.Очевидно, мой первый выбор - расширить структуру и добавить методы в производный класс.Это невозможно, так как сама структура объявлена ​​в заголовке как «forward», поэтому я получаю сообщение об ошибке «error: недопустимое использование неполного типа ...».Я получаю похожую ошибку, если я пытаюсь определить новую структуру с одним элементом старой структуры.Это отстой.

Тем не менее, я подумал, что мог бы сделать какую-нибудь хакерскую попытку с reinterpret_cast, чтобы заставить его работать в любом случае.Вот как это будет происходить:

//defined in header
struct A forward;
void do_something_with_A(A* a, int arg);

//defined in my wrapper
struct B {
  B* wrap(A* a) {return reinterpret_cast<B*>(a); }
  void do_something(int arg) {do_something_with_A(reinterpret_cast<A*>(this),arg); }
}

Если бы я добавил неявные преобразования из типа B в тип A, я подумал, что это может сработать почти , как если бы B был нулем- наследник данных A. Однако это, очевидно, поднимает вопрос: не определено ли это в C ++?Обычно я думаю, что доступ к элементу незаконно приведенной структуры будет неопределенным;в этом есть смысл.Тем не менее, я думаю, что reinterpret_casting от одного типа к другому, передавая этот указатель и затем возвращая его обратно, не делая ничего противозаконного между ними, было бы хорошо.Я бы также подумал, что способ, которым компилятор реализует не виртуальные члены структуры, будет создавать функцию

B::do_something(B* b, int arg)

и вызывать ее с соответствующим аргументом для B. Это затем сводится к предыдущему случаю, который по моимсомнительная логика в порядке.Поэтому я думаю, что вызов .do_something для структуры, которая на самом деле является reinterpret_cast A, будет в порядке.

Однако это ничего не говорит о том, что на самом деле говорит стандарт C ++.Любая помощь с этим?Кроме того, если у кого-то есть информация о том, насколько хорошо это будет работать практически (т. Е. «Каждый когда-либо созданный компилятор принимает это» или «это работает только с несколькими компиляторами»), это также было бы полезно, но немного менее.

Ответы [ 2 ]

2 голосов
/ 24 августа 2011

Я полагаю, что если вы приведете A * к B *, а затем снова вернете его к A *, то стандарт говорит, что вы в порядке.Это были бы reinterpret_casts, но не static_casts.

Но что именно не так с обычным решением?

class B
{
private:
  A* ptr;
public:
  B(A* p) : ptr(p) {}
  void do_something(int arg) { do_something_with_A(ptr,arg); }
};

Кажется, что оно так же эффективно, как и ваше решение, и менее опасно.

1 голос
/ 24 августа 2011

Я не верю, что это работает, если вы используете static_cast, потому что вы не можете static_cast между двумя совершенно не связанными типами классов.В частности, если у вас есть указатель типа A* и вы пытаетесь преобразовать его в указатель типа B*, static_cast будет успешным только в том случае, если это объявление действительно:

B* ptr(myAPtr);

илиесли B не является фактически производным от A (что не является).См. Спецификацию ISO § 5.2.2 для подробностей об особенностях этого.Если мы рассмотрим вышеупомянутое объявление, единственные возможные преобразования, которые могут быть применены здесь во всем §4, - это преобразования в §4.10, и из тех, которые могут быть применимы, это только преобразование из базовых в производные классы (§4.10 / 3), но здесь это не применимо, потому что A и B не являются связанными типами.

Единственный приведенный вами тип приведен здесь reinterpret_cast, и он не выглядиткак это будет работать либо.В частности, поведение приведения к иерархии классов имеет вид (§5.2.10 / 7)

Указатель на объект может быть явно преобразован в указатель на объект другого типа.65) За исключениемчто преобразование r-типа типа «указатель на T1» в тип «указатель на T2» (где T1 и T2 - типы объектов и где требования к выравниванию T2 не более строгие, чем требования к T1) и обратноисходный тип возвращает исходное значение указателя, результат такого преобразования указателя не определен.

Так что сразу нет гарантии, что что-нибудь будет работать, если два объекта имеют разные ограничения выравнивания, иВы не можете гарантировать, что это правда.Но предположим, что вы могли.В этом случае, однако, я считаю, что это на самом деле будет работать правильно!Вот рассуждения.Когда вы вызываете функцию-член объекта B, тогда срабатывает правило & 5.2.2 / 1), которое говорит, что функция не виртуальная:

[...]в вызове функции-члена обычно выбирается в соответствии со статическим типом выражения объекта.[...]

Хорошо, так что мы по крайней мере вызываем правильную функцию.А как насчет указателя this?Ну, согласно & 5.2.2 / 4:

[...] Если функция является нестатической функцией-членом, параметр «this» функции (9.3.2) должен быть инициализирован суказатель на объект вызова, преобразованный как бы путем явного преобразования типа (5.4).[...]

Преобразование типов, выполненное в последней части, представляет собой преобразование идентичности из B* в B*, поскольку это выбранный тип.Итак, вы вызвали правильную функцию с указателем this, установленным соответствующим образом.Ницца!Наконец, когда вы вернете reinterpret_cast к исходному типу, по более раннему правилу вы получите объект A*, и все пойдет как положено.

Конечно, этоработает, только если объекты имеют одинаковые требования выравнивания , и это не может быть гарантировано.Следовательно, вы не должны этого делать!

Надеюсь, это поможет!

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