Указатель / целочисленное арифметическое (не) определенное поведение

У меня есть следующий шаблон функции:

template <class MostDerived, class HeldAs>
HeldAs* duplicate(MostDerived *original, HeldAs *held)
{
// error checking omitted for brevity
MostDerived *copy = new MostDerived(*original);
std::uintptr_t distance = reinterpret_cast<std::uintptr_t>(held) - reinterpret_cast<std::uintptr_t>(original);
HeldAs *copyHeld = reinterpret_cast<HeldAs*>(reinterpret_cast<std::uintptr_t>(copy) + distance);
return copyHeld;
}

Цель состоит в том, чтобы продублировать объект определенного типа и вернуть его, «удерживаемый» тем же подобъектом, что и входные данные. Обратите внимание, что в принципе HeldAs может быть неоднозначным или недоступным базовым классом MostDerivedтак что никакой актерский состав не может помочь здесь.

Это мой код, но он может быть использован с типами вне моего контроля (т.е. я не могу изменить MostDerived или же HeldAs). Функция имеет следующие предварительные условия:

  • *original имеет динамический тип MostDerived
  • HeldAs является MostDerived или прямой или косвенный базовый класс MostDerived (игнорируя cv-квалификацию)
  • *held относится к *original или один из его подобъектов базового класса.

Давайте предположим, что предварительные условия выполнены. Есть ли duplicate определили поведение в таком случае?

C ++ 11 [expr.reinterpret.cast] говорит (выделение жирным шрифтом мое):

4 Указатель может быть явно преобразован в любой целочисленный тип, достаточно большой для его хранения. Функция отображения
реализации. [ Замечания: Это должно быть неудивительно для тех, кто знает структуру адресации
базовой машины. —Конечная записка ] …

5 Значение целочисленного типа или типа перечисления может быть явно преобразовано в указатель. Указатель преобразован
к целому числу достаточного размера (если таковое существует в реализации) и обратно к тому же типу указателя
будет иметь свое первоначальное значение; Отображения между указателями и целыми числами определяются реализацией.
[ Замечания: За исключением случаев, описанных в 3.7.4.3, результатом такого преобразования не будет безопасный указатель
значение. —Конечная записка ]

Хорошо, допустим, мой компилятор — GCC (или Clang, поскольку он использует определения поведения, определенного GCC). квотирование GCC документы глава 5 на C ++ определяемое реализацией поведение:

… Некоторые варианты описаны в соответствующем документе для языка Си. Увидеть Реализация С. …

На глава 4.7 (Реализация C, массивы и указатели):

Результат преобразования указателя в целое число или наоборот (C90 6.3.4, C99 и C11 6.3.2.3).

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

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

Все идет нормально. Казалось бы, так как я использую std::uintptr_t который гарантированно будет достаточно большим для любого указателя, а так как я имею дело с одними и теми же типами, copyHeld следует указать на то же HeldAs подобъект *copy как held указывал на внутри *original,

К сожалению, есть еще один абзац в документах GCC:

При приведении от указателя к целому и обратно, результирующий указатель должен ссылаться на тот же объект, что и исходный указатель, в противном случае поведение не определено. То есть нельзя использовать целочисленную арифметику, чтобы избежать неопределенного поведения арифметики с указателями, как это запрещено в C99 и C11 6.5.6 / 8.

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

У меня в основном три вопроса:

  1. Является ли мое чтение правильным и поведение duplicate не определено?

  2. Что это за неопределенное поведение? Формально «неопределенный, но все равно будет делать то, что вы хотите» или «ожидать случайных аварий и / или спонтанного самосожжения»?

  3. Если это действительно не определено, есть ли способ сделать это хорошо определенным (возможно, зависящим от компилятора) способом?

Хотя мой вопрос ограничен поведением GCC (и Clang) в том, что касается компиляторов, я бы приветствовал ответ, который рассматривает все виды платформ HW, от обычных настольных компьютеров до экзотических.

5

Решение

Обычная схема для этого — поставить clone() в базовом классе.
Затем каждый производный класс может реализовать свою собственную версию клона.

class Base
{
public:
virtual Base*  clone() = 0;
};

class D: public Base
{
virtual Base*  clone(){  return new D(*this);}
};
0

Другие решения


По вопросам рекламы [email protected]