Ново поставяне и присвояване на клас с постоянен член

Защо е това недефинирано поведение?

struct s
{
    const int id; // <-- const member

    s(int id):
        id(id)
    {}

    s& operator =(const s& m) {
        return *new(this) s(m); // <-- undefined behavior?
    }
};

(Цитат от стандарта би бил хубав).

Този въпрос произтича от този отговор.


person DaBler    schedule 24.11.2017    source източник
comment
const int id; казва, че стойността на id никога няма да се промени. И после го сменяш?   -  person Bo Persson    schedule 24.11.2017
comment
Странно е да искате да мутирате нещо, което съзнателно сте направили непроменливо.   -  person molbdnilo    schedule 24.11.2017
comment
@BoPersson: Друг изглед е, че създавам нов обект на същото място.   -  person DaBler    schedule 24.11.2017
comment
Чета стандарта c++17 от 30 минути и все още не мога да намеря набор от правила, забраняващи двойното конструиране на обект.   -  person YSC    schedule 24.11.2017
comment
Спомням си, че това е законно. @BoPersson const се отнася само за живота на обекта.   -  person Passer By    schedule 24.11.2017
comment
@PasserBy не е ли легално само ако по някакъв начин извикате деструктора на обекта между двете извиквания на неговия конструктор? Наистина не успявам да го намеря споменат в стандарта. Трябва да е скрито някъде.   -  person YSC    schedule 24.11.2017
comment
Неизвикването на @YSC деструктори не е недефинирано поведение. Извикването на деструктор на невалиден обект е.   -  person Passer By    schedule 24.11.2017
comment
@YSC Ако деструкторът е тривиален (както в този случай), тогава е законно да не го извиквате. timsong-cpp.github.io/cppwp/basic.life# 5.изречение-1   -  person Rakete1111    schedule 24.11.2017
comment
@Rakete1111 това е отговор!   -  person YSC    schedule 24.11.2017
comment
@YSC Не мисля така. Казва, че UB няма да възникне от неизвикване на деструктори, което в най-добрия случай е много частично.   -  person Passer By    schedule 24.11.2017
comment
@PasserBy какво? създадох стая за чат, изглежда има някъде недоразумение   -  person YSC    schedule 24.11.2017
comment
@YSC Не знам дали всъщност съм прав, ако това се отнася дори ако обект в обект е const, защото не знам дали timsong-cpp.github.io/cppwp/basic.life#10 изглежда се прилага.   -  person Rakete1111    schedule 24.11.2017
comment
@Rakete1111 [basic.life]/10 е за const обекти, а не обект с const членове, нали?   -  person YSC    schedule 24.11.2017
comment
@YSC Това е общо взето, не знам :( Със сигурност обаче изглежда като първото   -  person Rakete1111    schedule 24.11.2017
comment
Четейки [basic.life]/9, изрично се казва, че е добре?   -  person YSC    schedule 24.11.2017
comment
@YSC Константен член е константен обект, нали?   -  person curiousguy    schedule 07.07.2018


Отговори (1)


Няма нищо, което да прави показания кодов фрагмент по същество UB. Почти сигурно е обаче, че UB ще последва веднага при нормална употреба.

От [basic.life]/8 (акцентът е мой)

Ако след изтичане на живота на даден обект и преди съхранението, което обектът е заемал, да бъде повторно използвано или освободено, се създава нов обект на мястото за съхранение, което е заемал оригиналният обект, указател, който сочи към оригиналния обект, препратка, която отнесено към оригиналния обект, или името на оригиналния обект автоматично ще препраща към новия обект и след като животът на новия обект започне, може да се използва за манипулиране на новия обект, ако:

  • съхранението за новия обект точно покрива мястото за съхранение, което оригиналният обект е заемал, и

  • новият обект е от същия тип като оригиналния обект (игнорирайки cv-квалификаторите от най-високо ниво) и

  • типът на оригиналния обект не е const-квалифициран и, ако е тип клас, не съдържа никакви нестатични данни, чийто тип е const-квалифициран или референтен тип, и

  • оригиналният обект е най-производен обект от тип T, а новият обект е най-производен обект от тип T (т.е. те не са подобекти от базов клас).

Тъй като в s има член const, използването на оригиналната променлива след извикване на operator= ще бъде UB.

s var{42};
var = s{420};         // OK
do_something(var.id); // UB! Reuses s through original name
do_something(std::launder(&var)->id);  // OK, this is what launder is used for
person Passer By    schedule 24.11.2017
comment
И така, разбирам ли правилно, че няма законен начин за достъп до членовете преди C++17? - person DaBler; 24.11.2017
comment
@DaBler Технически има. Но силно препоръчвам да не го правите. auto& ref = (var = s{420});. И след това използвайте ref - person Passer By; 24.11.2017
comment
ОК разбрах. Благодаря. - person DaBler; 24.11.2017
comment
@DaBler Технически, при стриктно четене на std, при общи реализации, използването на указателен обект винаги е гарантирано да работи след този трик за промяна на константата. Все още е така, стига указателят да съдържа само адрес (номер). Това обаче е педантично и със сигурност НЕ е предвидено и хората, чиято работа е да тълкуват std, няма да го подкрепят. Никой компилатор няма да направи всичко възможно, за да поддържа това педантично четене. - person curiousguy; 07.07.2018