reinterpret_cast для пустого дочернего класса - PullRequest
0 голосов
/ 01 апреля 2020

Возможно, этот вопрос поднимался несколько раз, но я до сих пор не могу найти обоснованного обоснованного ответа. Рассмотрим следующий фрагмент кода:

struct A {virtual int vfunc() = 0;};
struct B {virtual ~B() {}};
struct C {void *cdata;};
//...
struct Z{};

struct Parent:
  public A,
  virtual B,
  private C,
  //...
  protected Z
{
  int data;
  virtual ~Parent(){}
  virtual int vfunc() {return 0;} // implements A::vfunc interface
  virtual void pvfunc() {};
  double func() {return 0.0;}
  //...etc
};

struct Child:
  public Parent
{
  virtual ~Child(){}
  int more_data;
  virtual int vfunc() {return 0;} // reimplements A::vfunc interface
  virtual void pvfunc() {};// implements Parent::pvfunc interface
};

template<class T>
struct Wrapper: public T 
{
 // do nothing, just empty
};

int main()
{
  Child ch;
  Wrapper<Child> &wr = reinterpret_cast<Wrapper<Child>&/**/>(ch);
  wr.data = 100;
  wr.more_data = 200;
  wr.vfunc();
  //some more usage of wr...
  Parent pr = wr;

  pr.data == wr.data; // true?
  //...

  return 0;
}

В основном это показывает приведение к ссылке на фиктивного дочернего элемента class Wrapper и использование членов его классов-предков.

Вопрос в том, допустим ли этот код по стандарту? если нет, то что именно нарушает?

PS: Не предоставляйте ответы типа "это неправильно на стольких уровнях" и т.п., пожалуйста. Мне нужны точные цитаты из стандарта, подтверждающего точку.

Ответы [ 2 ]

3 голосов
/ 01 апреля 2020

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

Однако, чтобы ответить на реальный вопрос - это совершенно неопределенное поведение. В C ++ 17 это раздел 8.2.10 [expr.reinterpret.cast]. Используйте фразу в скобках, чтобы получить соответствующий раздел для предыдущих стандартов.


РЕДАКТИРОВАТЬ Я думал, что краткого ответа будет достаточно, но запрашивались дополнительные детали. Я не буду упоминать другие проблемы с кодом, потому что они просто запутают воду.

Здесь есть несколько ключевых проблем. Давайте сосредоточимся на reinterpret_cast.

Child ch;
Wrapper<Child> &wr = reinterpret_cast<Wrapper<Child>&/**/>(ch);

Большая часть формулировки в spe c использует указатели, поэтому, основываясь на 8.2.10 / 11, мы немного изменим пример кода на этот.

Child ch;
Wrapper<Child> *wr = reinterpret_cast<Wrapper<Child>*>(&ch);

Здесь приведена часть стандарта для этого обоснования в кавычках.

Выражение glvalue типа T1 может быть приведено к типу «ссылка на T2», если выражение типа « указатель на T1 »может быть явно преобразован в тип« указатель на T2 »с помощью reinterpret_cast. Результат ссылается на тот же объект, что и источник glvalue, но с указанным типом. [Примечание: то есть для l-значений эталонное приведение reinterpret_cast (x) имеет тот же эффект, что и преобразование * reinterpret_cast (& x) со встроенными операторами & и * (и аналогично для reinterpret_cast (x)). - примечание конца] Временное создание не производится, копирование не производится, а конструкторы (15.1) или функции преобразования (15.3) не вызываются.

Одна тонкая маленькая часть стандарта - 6.9.2 / 4, который позволяет в некоторых особых случаях обрабатывать указатель на один объект, как если бы он указывал на объект другого типа.

Два объекта a и b являются взаимозаменяемыми по указателю, если:

(4.1) - это один и тот же объект, или

(4.2) - один является объектом объединения стандартной компоновки, а другой является нестационарным c членом данных этого объекта (12.3 ) или

(4.3) - один из них является объектом класса стандартной компоновки, а другой - первым не-статическим c членом данных этого объекта, или, если у объекта нет нестатического c члены данных, первый подобъект базового класса этого объекта (12.2) или

(4.4) - существует объект c такой, что a и c являются взаимозаменяемыми по указателю, а c и b преобразуются указателем.

Если два o bject являются взаимозаменяемыми по указателю, тогда они имеют одинаковый адрес, и можно получить указатель на один из указателя на другой через reinterpret_cast (8.2.10). [Примечание: объект массива и его первый элемент не являются взаимозаменяемыми по указателю, даже если они имеют один и тот же адрес. - конец примечания]

Однако ваш случай не соответствует этому критерию, поэтому мы не можем использовать это исключение для обработки указателя на Child, как если бы он был указателем на Wrapper<Child>.

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

Обратите внимание на последнее предложение 8.2.10 / 1

Преобразования, которые могут быть выполнены явно с использованием reinterpret_cast, перечислены ниже. Никакое другое преобразование не может быть выполнено явно с использованием reinterpret_cast.

Ниже приведены 10 абзацев.

В параграфе 2 говорится, что reinterpret_cast не может отбрасывать константу. Не наша забота.

В параграфе 3 говорится, что результат может давать или не давать другое представление.

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

Пункт 6 посвящен приведению указателей на функции.

Пункт 8 посвящен преобразованию указателей функций и указателей объектов.

Пункт 9 касается преобразования значений нулевых указателей.

Параграф 10 посвящён конвертации между указателями на элементы.

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

Таким образом, остается параграф 7, в котором говорится. 1068 *

Указатель объекта может быть явно преобразован в указатель объекта другого типа. 73 Когда значение v типа указателя объекта преобразуется в тип указателя объекта «указатель на cv T», результатом является static_cast (static_cast (v)). [Примечание: преобразование значения типа «указатель на T1» в тип «указатель на T2» (где T1 и T2 являются типами объектов, а требования к выравниванию для T2 не более строгие, чем требования для T1) и обратно к его исходному типу возвращает исходное значение указателя - конец примечания]

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

Здесь 6.10 / 8

Если программа пытается получить доступ к сохраненному значение объекта через glvalue, отличное от одного из следующих типов, поведение не определено:

(8.1) - тип объекта Dynami c,

(8.2) - cv-квалифицированная версия типа объекта Dynami c,

(8.3) - тип, подобный (как определено в 7.5) типу объекта Dynami c,

(8.4) - тип, который является типом со знаком или без знака, соответствующим типу динамического c объекта,

(8.5) - тип, который является типом со знаком или без знака, соответствующим cv версия объекта типа Dynami c,

(8.6) - агрегатный или объединенный тип, включающий в себя один из вышеупомянутых типов среди своих элементов или нестатических c членов данных (включая , рекурсивно, элемент или не стати c член данных субагрегата или объединенного объединения),

(8.7) - тип, который является (возможно, cv-квалифицированным) типом базового класса типа Dynami c объекта,

(8.8) - тип char, unsigned char или std :: byte.

Ваш случай не удовлетворяет ни одному из них.

В вашем случае вы принимаете указатель на один тип и заставляющий компилятор делать вид, что он указывает на другой тип. Неважно, как эти два взгляда выглядят - знаете ли вы, что полностью стандартному компилятору, который соответствует требованиям, не нужно помещать данные для производного класса после данных для базового класса? Эти детали НЕ являются частью стандарта C ++, но являются частью ABI, которую реализует ваш компилятор.

На самом деле, очень мало случаев, когда использование reinterpret_cast для чего-либо другого, кроме переноса указателя и последующего приведения его обратно к его оригинальный тип, который не вызывает неопределенного поведения.

2 голосов
/ 01 апреля 2020

Как указано в другом ответе, это обсуждение относится к разделу 8.2.10 [expr.reinterpret.cast] стандарта C ++ 17.

Предложение 11 этого раздела объясняет, что для ссылок на объекты мы может иметь те же причины, что и для указателей на объекты.

Wrapper<Child> &wr = reinterpret_cast<Wrapper<Child>&/**/>(ch);
or
Wrapper<Child> *wr = reinterpret_cast<Wrapper<Child>*/**/>(&ch);

В предложении 7 этого раздела объясняется, что для указателей на объекты reinterpret_cast можно рассматривать как два static_cast в последовательности (через void *).

В конкретном c случае этого вопроса тип Wrapper<Child> фактически наследуется от Child, поэтому достаточно одного static_cast (не нужно ни два static_cast, ни reinterpret_cast).

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


hum ...

Если подумать, думаю, я совершенно не прав !
(static_cast неверен, я прочитал его неправильно)

* 10 23 * Если бы у нас было
Wrapper<Child> wc=...
Child *pc=&wc;
Wrapper<Child> *pwc=static_cast<Wrapper<Child>*>(pc);

, то static_cast (тогда reinterpret_cast) был бы верным, потому что он возвращается к исходному типу.

Но в вашем примере original исходный тип был не Wrapper<Child>, а Child.
Даже если это маловероятно, компилятор не запрещает добавлять некоторые скрытые элементы данных в Wrapper<Child>.
Wrapper<Child> не является пустой структурой , он участвует в иерархии с динамическим полиморфизмом c, и компилятор может использовать любое решение под капотом.
Таким образом, после reinterpret_cast оно становится неопределенным, поскольку адрес, сохраненный в указателе (или ссылке), будет указывают на некоторые байты с компоновкой Child, но следующий код будет использовать эти байты с компоновкой Wrapper<Child>, которая может отличаться.

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