среда, 23 августа 2023 г.

[prog.c++] Пример того, как NRVO(RVO) оптимизация может к вам в код UB подсадить

В процессе одного из споров на RSDN-е родился пример того, как компилятор, применив NRVO, приводит к неожиданному поведению.

Под катом полный код примера. Пока же пару слов о том, на что смотреть.

Есть простой класс entity, который содержит внутри себя целочисленное значение. Изменить это целочисленное значение можно только вызывав метод entity::update. Но это не const-метод, т.е. для константного entity его вызывать нельзя.

Фокус с entity в том, что есть еще и entity_registry, т.е. реестр объектов entity. Если в конструктор entity передать ссылку на entity_registry, то entity себя в реестре зарегистрирует. А в деструкторе -- вычеркнет.

Пока объект находится в реестре, его можно изменить через реестр: вызываем метод entity_registry::update и реестр вызывает update для всех зарегистрированных в нем объектов.

Но важно отметить, что если объект entity создавался без передачи ему в конструктор ссылки на реестр, то он ни в каких реестрах не регистрируется, поэтому через реестр его изменить нельзя. Так что в такой ситуации:

void check() {
   entity_registry registry;
   entity e1{registry};

   entity e2; // Не в реестре.

   entity e3{e1}; // Не в реестре.

   registry.update(25);

   std::cout << e1.value() << ", " << e2.value() << ", " << e3.value() << std::endl;
}

Мы получим вполне ожидаемое:

add to registry: 0x7ffd7b567c18
25, 0, 0
removed from registry: 0x7ffd7b567c18

Т.е. объект e1 добавил себя в реестр, потом мы получили три предсказуемых значения (два нулевых, т.к. никто не менял e2 и e3), затем e1 изъял себя из реестра.

А теперь внимание, вопрос: что будет во в таком случае?

entity make_entity(entity_registry & registry) {
   entity ent{registry};
   ent.update(42);
   return ent;
}

void demo() {
   entity_registry registry;
   const entity ent = make_entity(registry);

   registry.update(100);

   std::cout << ent.value() << std::endl;
}

Можем ли мы изменть значение константного ent внутри функции demo через вызов update у реестра?