Размещение новых разрывов, констант и ссылок?

После обсуждения мой ответ на этот вопрос, по-видимому:

следующий код разрешен

struct Foo {
    int x;
};

Foo f;
Foo & f_ref = f;

(&f) -> ~Foo ();
new (&f) Foo ();

int x = f_ref .x;

но следующий код запрещен

struct Foo {
    const int & x;           // difference is const reference
    Foo (int & i) : x(i) {}
};

int i;
Foo f (i);
Foo & f_ref = f;

(&f) -> ~Foo ();
new (&f) Foo (i);

int x = f_ref .x;

Из-за 3,8 долл. США / 7

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

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

Я могу понять, как ссылка на f.x может быть признана недействительной, когда f перестает существовать, но я не понимаю, почему f_ref следует признать недействительным только потому, что один из его членов является константой и / или ссылкой, а не иначе: это была ссылка на Foo до и является ссылкой на Foo после.

Может кто-нибудь объяснить причину этого условия?

Редактировать

Спасибо за ответы. Я не покупаю аргумент «гарантия, что он не изменится», потому что в настоящее время мы не разрешаем оптимизаторам кэшировать референции, например:

struct Foo {
    const int & x;
    Foo (const int & i) : x(i) {}
    void do_it ();
};

int i;
Foo f (i);
const int & ii = f.x;

f .do_it (); // may modify i
std :: cout << ii; // May NOT use cached i

Я не понимаю, как do_it разрешено аннулировать ссылочные значения, но operator new нет - Точки последовательности делают кешированные значения недействительными: почему следует исключить удаление / новое размещение?


person spraff    schedule 28.09.2011    source источник
comment
Неважно, OP редактировал код.   -  person Kerrek SB    schedule 28.09.2011
comment
Я не понимаю, как do_it мог изменить i?   -  person UncleBens    schedule 28.09.2011
comment
@uncleBens: например, const_cast<int &>(x) = 1; в do_it совершенно нормально, учитывая, что референдум x на самом деле является изменяемым объектом int. Если это то, что делает do_it, тогда было бы ошибкой (UB) делать const int i; Foo f(i); f.do_it();, но с неконстантными i все в порядке.   -  person Steve Jessop    schedule 28.09.2011


Ответы (4)


Я считаю, что мотивация состоит в том, чтобы позволить компилятору кэшировать значения объектов const (обратите внимание, что const objects, а не просто ссылки на указатели на константу и ссылку на константу) и адреса референсы ссылок через вызовы неизвестного кода.

Во втором примере компилятор может «видеть», во-первых, что объект был создан и уничтожен, а во-вторых, что он был воссоздан с использованием того же значения. Но авторы стандарта хотели, чтобы компиляторам было разрешено включать этот код:

struct Foo {
    const int & x;
    Foo (int & i) : x(i) {}
};

int i = 1;
Foo f(i);

some_function_in_another_TU(&f);

std::cout << f.x;

В это:

struct Foo {
    const int & x;
    Foo (int & i) : x(i) {}
};

int i = 1;
Foo f(i);

some_function_in_another_TU(&f);

std::cout << i;           // this line is optimized

потому что ссылочный элемент f не может быть переустановлен и, следовательно, должен по-прежнему ссылаться на i. Операция уничтожения и построения нарушает неповторяемость ссылочного члена x.

Эта оптимизация не должна вызывать особых споров: рассмотрим следующий пример, в котором используется объект const, а не объект с const или ссылочным членом:

const int i = 1;
some_function_in_another_TU(&i);
std::cout << i;

Здесь i - константа времени компиляции, some_function_in_another_TU не может корректно уничтожить ее и создать на ее месте другой int с другим значением. Таким образом, компилятору должно быть разрешено генерировать код для std::cout << 1;. Идея состоит в том, что то же самое должно быть верно по аналогии для константных объектов других типов и для ссылок.

Если вызов неизвестного кода может переустановить ссылочный элемент или изменить значение элемента данных const, то полезный инвариант языка (ссылки никогда не переустанавливаются, а константные объекты никогда не изменяют свои значения) будет нарушен.

person Steve Jessop    schedule 28.09.2011
comment
Не могли бы вы прокомментировать правку внизу вопроса? Спасибо. - person spraff; 28.09.2011
comment
@spraff: вы не различаете кеширование значения реферада и кеширование местоположения реферада. Мой пример с std::cout << i делает последнее. В вашем примере с May NOT использовать cached i мне кажется, что значение i не может быть кэшировано, и это правда. Однако расположение референса ii известно и может быть кэшировано, может изменяться только значение. Я вижу, что ваш пример не имеет отношения к моему. - person Steve Jessop; 28.09.2011
comment
В моем примере с std :: cout ‹< я делаю последнее. - ой, извините, только что понял, что у меня есть два примера с std::cout << i;. В этом комментарии я имел в виду свой пример, где std::cout << f.x; может быть корректно преобразован компилятором в std::cout << i;. - person Steve Jessop; 28.09.2011

Насколько я могу судить, это просто вопрос семантической корректности и допущений, которые может сделать оптимизатор. Учти это:

Bar important, relevant;

Foo x(important);  // binds as const-reference

Zoo z(x);  // also binds as const reference

do_stuff(z);

x.~Foo();
::new (&x) Foo(relevant);  // Ouch?

Объект z может разумно ожидать, что его Foo ссылка на член будет постоянной и, таким образом, относится к important. Как сказано в стандарте, разрушение плюс новое построение в последних двух строках «автоматически обновляет все ссылки, чтобы ссылаться на (логически) новый объект», поэтому теперь константная ссылка внутри z изменилась, несмотря на обещание быть постоянным.

Чтобы избежать этого грубого нарушения корректности const, вся реконструкция на месте запрещена.

person Kerrek SB    schedule 28.09.2011
comment
+1 за срок backstabbing violation. Это прекрасный пример предательства. - person Nawaz; 28.09.2011

Оптимизация. Предположим, у меня есть:

struct Foo
{
    int const x;
    Foo( int init ) : x( init ) {}
};

int
main()
{
    Foo a( 42 );
    std::cout << a.x << std::endl;
    new (&a) Foo( 3 );
    std::cout << a.x << std::endl;
    return 0;
}

Компилятор, увидев объект const int, вправе предположить, что значение не меняется; оптимизатор может просто сохранить значение в регистре в новом месте размещения и вывести его снова.

Обратите внимание, что ваш пример на самом деле совсем другой. Член данных имеет тип int const&; это ссылка (а ссылки всегда константы), поэтому компилятор может предположить, что ссылка всегда относится к одному и тому же объекту. (Значение этого объекта может измениться, если сам объект также не является константой.) Однако компилятор не может делать таких предположений о значении объекта, на который он ссылается, поскольку i (в вашем случае) может явно измениться. Именно тот факт, что сама ссылка (как и все ссылки) является неизменной, вызывает здесь неопределенное поведение, а не const, который вы написали.

person James Kanze    schedule 28.09.2011
comment
то, к чему это относится, может не измениться - я согласен с тем, что вы говорите, но мой опыт показывает, что такие утверждения могут быть неверно истолкованы. Значение объекта, на который он ссылается, может измениться, но объект, к которому он относится, не может измениться. Некоторые люди читают, что то, к чему он относится, не может измениться, и думают, что вы (ложно) утверждаете, что (значение) объекта, к которому он относится, не может измениться, тогда как на самом деле вы (правильно) утверждаете, что он все еще ссылается к одному и тому же объекту до и после. - person Steve Jessop; 28.09.2011
comment
@Steve Jessop Моя формулировка кажется довольно неудачной, поскольку ее можно легко понять двумя разными способами. Я поправлю его, чтобы (надеюсь) он был менее двусмысленным. - person James Kanze; 29.09.2011

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

person David Schwartz    schedule 28.09.2011
comment
Если x является константной ссылкой на неконстантный y, x все еще может измениться, потому что y может измениться - ограничение состоит в том, что вы не можете использовать x как средство для изменения y. - person spraff; 28.09.2011
comment
Да, точно. Вы думали, что это как-то не согласуется с тем, что я сказал? (Если ваше единственное соединение с чем-то является ссылкой на него константой, вы не должны его изменять. В этом случае ссылка на константу - единственное соединение.) - person David Schwartz; 28.09.2011
comment
Согласованный. Это не препятствует тому, чтобы что-то еще его можно было изменить, а именно этим и занимается весь этот бизнес. - person spraff; 28.09.2011
comment
Не совсем, потому что что-то еще связано с этим объектом только через константную ссылку. При этом нельзя разрешать изменять свой срок службы. То есть у вас есть X, который имеет постоянную ссылку на Y. Таким образом, вам не должно быть разрешено изменять время жизни Y. Таким образом, вы не можете продлить время жизни X, потому что это изменит время жизни Y. - person David Schwartz; 28.09.2011