Размещение нового на этом указателе

В двух операторах печати печатаются разные числа. Насколько я понимаю, я не делаю здесь никаких хитрых const_cast, поэтому я не уверен, что я мог совершить.

  1. Правильно ли сформирован этот код?

  2. Может ли компилятор полагаться на тот факт, что A::num - это const, поэтому ему разрешено печатать одно и то же число?

Код:

struct A
{
    const int num = 100;

    A() {}    

    A(int in) : num{in} {}

    void call()
    {
        new (this) A{69};
    }
};

int main()
{
    A a;    
    std::cout << a.num << '\n';
    a.call();
    std::cout << a.num << '\n';
}

Каков результат, и чего вы ожидаете от него?

John Ilacqua 10.11.2018 03:15

Я собираюсь рискнуть и сказать, что это почти наверняка неопределенное поведение placement new в памяти, которая уже содержит существующий экземпляр класса. Вам, вероятно, это сойдет с рук (хотя я предполагаю, что компилятор вас предупредит), потому что не задействованы выделения памяти или виртуальные функции, но это кажется самой изворотливой из хитрых идей.

ShadowRanger 10.11.2018 03:16

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

John Ilacqua 10.11.2018 03:25
[basic.life] «Создание нового объекта в хранилище, которое занимает константный полный объект со статической, потоковой или автоматической продолжительностью хранения, или в хранилище, которое такой константный объект занимал до окончания своего времени жизни, приводит к неопределенному поведению».
Raymond Chen 10.11.2018 04:46

@ShadowRanger Вы не поверите, но вам разрешено повторно использовать хранилище живых объектов! [basic.life] "Программа может закончить время жизни объекта, повторно используя память, которую занимает объект, или явно вызвав деструктор для объекта с нетривиальным деструктором. Для объекта типа класса с нетривиальным деструктором, программе не требуется явно вызывать деструктор перед повторным использованием или освобождением памяти, занимаемой объектом ... "

Raymond Chen 10.11.2018 04:50

@RaymondChen Это правда, даже если константен только член? «полный объект» определяется в [intro.object] как «объект, который не является подобъектом какого-либо другого объекта, называется законченным объектом».

user10605163 10.11.2018 05:09

@RaymondChen a, а не const, поэтому новое размещение не UB.

Rakete1111 10.11.2018 06:40

@ShadowRanger "определенно неопределенное поведение для размещения нового в памяти, которая уже содержит существующий экземпляр класса" нет, в C++ совершенно законно делать что-либо с памятью существующих объектов (кроме некоторых глобальных константных объектов); но это уничтожает эти ранее существовавшие объекты, они больше не существуют как объекты. Виртуальные функции "потому что не задействованы выделения памяти или виртуальные функции" здесь не имеют значения, но попытка использовать объект, который не существует (потому что вы его стерли), делает

curiousguy 10.11.2018 07:28

«Я не делаю изворотливого const_cast». На любом языке, допускающем низкоуровневое программирование, с прямым использованием указателя на память и ручным управлением памятью, у вас будет возможное неопределенное поведение без приведения указателя или другой «хитрой» конструкции. Это прямое следствие доверия программисту к низкоуровневой памяти и управлению сроком службы. Без него C++ не был бы C++.

curiousguy 10.11.2018 07:32
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
9
149
1

Ответы 1

Нет, в вашем коде есть UB. Удалите const на num, и вы больше не получите UB.

Проблема в том, что стандарт гарантирует, что объект const не изменится. Но если вы повторно используете одно и то же хранилище, вы можете каким-то образом «модифицировать» объект const.

[basic.life] p8 явно запрещает это, говоря, что старое имя объекта относится к новому объекту только при определенных условиях. Один из них заключается в том, что в вашем классе нет константных членов. Итак, по расширению ваш второй a.num - это UB, поскольку a относится к старому разрушенному объекту.

Однако есть два способа избежать этого UB. Во-первых, вы можете сохранить указатель на новый объект:

struct A *new_ptr;
struct A {
  // [...]
  void call() {
      new_ptr = new (this) A{69};
  }
};

int main()
{
    A a;    
    std::cout << a.num << '\n';
    a.call();
    std::cout << new_ptr->num << '\n'; // ok
}

Или используйте std::launder:

std::cout << std::launder(&a)->num << '\n'; // second access

да. Но обратите внимание, что согласно теории (постулируемой стандартом std) указатели являются POD (или тривиальными типами), использование std::launder по своей сути избыточно, поскольку все тривиальные типы данного типа и данное побайтовое представление должны иметь одно и то же намеренное значение, а не одно и то же числовое значение. . Или банальный шрифт - действительно бесполезное понятие.

curiousguy 10.11.2018 07:35

Другие вопросы по теме