Замена объекта на объект того же типа

Правильно ли определено следующее?

#include <iostream>
#include <string.h>

using namespace std;

struct Const {
    const int i; 
    Const (int i) : i(i) {}
    int get0() { return 0; } // best accessor ever!
};

int main() {
    Const *q,*p = new Const(1);
    new (p) Const(2);
    memcpy (&q, &p, sizeof p);
    cout << q->i;
    return 0;
}

Обратите внимание, что после создания второго Const, p семантически (намеренно?) Не указывает на новый объект, а первый отсутствует, поэтому его можно использовать «как void*». Но второй объект создается по тому же адресу, поэтому битовая комбинация p представляет адрес нового объекта.

КОММЕНТАРИЙ

new (p) Const(2) удалите старый объект, хранящийся в p, поэтому указатель больше не действителен, кроме как указатель на хранилище (void*).

Я хочу восстановить значение p как Const*.

КОММЕНТАРИЙ 2

После p->~Const() или memset (p, 0, sizeof *p) становится ясно, что p не указывает на действительный объект, поэтому p можно использовать только как указатель на хранилище (void* или char*), например, для восстановления другого объекта. В этот момент p->get0() не допускается.

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

Моя интуиция такова: В любом случае старый объект исчез, а p указывает на старый объект, а не на новый.

Я ищу подтверждения или опровержения на основе стандарта.

СМОТРИТЕ ТАКЖЕ

Я задал, по сути, тот же вопрос об указателях в C и C ++:

Пожалуйста, прочтите эти обсуждения, прежде чем отвечать «это смешно».


person curiousguy    schedule 17.08.2015    source источник
comment
memcpy должно быть эквивалентно q = p простому назначению указателя, не так ли?   -  person Daniel Jour    schedule 17.08.2015
comment
@DanielJour Я так не думаю. p больше не является допустимым указателем на объект типа Const.   -  person curiousguy    schedule 17.08.2015
comment
Почему нет? Он указывает на правильно выровненную область памяти, в которой был построен объект правильного типа. (Кстати, вам, вероятно, следует добавить операцию удаления)   -  person Daniel Jour    schedule 17.08.2015
comment
@DanielJour Объект, на который он указывал, мертв, стерт. Дтор тривиален; говорят, есть нетривиальный дтор и я его называю, если хотите. (Я не думаю, что это имеет значение.)   -  person curiousguy    schedule 17.08.2015
comment
@DanielJour Assignment копирует значение указателя, я хочу скопировать только его битовый шаблон.   -  person curiousguy    schedule 17.08.2015
comment
Присвоение копирует значение указателя, я хочу скопировать только его битовый шаблон. Значение указателя - это значение битов, которые вы копируете с помощью memcpy.   -  person Chris Beck    schedule 17.08.2015
comment
Указатель зависит от состояния программы. Указатель типа p - это простой тип значения, очень похожий на целое число. Значение *p зависит от состояния программы, но значение p является самодостаточным.   -  person Chris Beck    schedule 17.08.2015
comment
N4430 решает аналогичную проблему.   -  person Kerrek SB    schedule 17.08.2015
comment
@ChrisBeck указатель типа p - это простой тип значения, очень похожий на целое число Очевидно, нет. Целое число никогда не является недействительным.   -  person curiousguy    schedule 17.08.2015
comment
@ChrisBeck: Нет. Например, фрагмент void *p=malloc(1); free(p); printf("%p", p); - это UB, потому что вы даже не можете распечатать значение p после того, как оно было передано в free.   -  person 6502    schedule 17.08.2015
comment
@curiousguy Думаю, у вас есть хороший вопрос. Но я думаю, вам придется немного объяснить это, потому что я думаю, что большинство людей этого не понимают.   -  person juanchopanza    schedule 17.08.2015
comment
@curiousguy Можно возразить, что неинициализированный int недействителен в том смысле, что читать из него UB. Но после инициализации он остается в силе.   -  person juanchopanza    schedule 17.08.2015
comment
@juanchopanza Объект int может быть недопустимым, но значение int не может, AFAIK.   -  person curiousguy    schedule 17.08.2015
comment
curiousguy: iiuc Единственная часть вашего примера, которая undefined, это то, что соответствующая реализация может изменять значение p произвольно, когда вы звоните бесплатно. если вы используете memcpy вместо q=p, это ничего не меняет, вы все равно копируете неопределенное значение в любом случае, так что вся эта memcpy вещь является неверным направлением   -  person Chris Beck    schedule 17.08.2015
comment
@ChrisBeck Не уверен, что вы имеете в виду под соответствующей реализацией, разрешено изменять значение p, когда вы звоните бесплатно. В любом случае я мог бы сохранить резервные копии p, если бы это было так.   -  person curiousguy    schedule 17.08.2015
comment
любопытный парень: посмотрев стандартный esp. Раздел 3.8 Я вижу, что это хороший вопрос, о котором я мало что знаю, извините, что доставил вам неприятности. Я действительно думаю, что memcpy выглядит странно.   -  person Chris Beck    schedule 17.08.2015
comment
На самом деле, когда я смотрю на материал в 3.8.5, следующие за ним примеры кода, и особенно на то, что сказано в 3.8.5.2, мне интересно, это UB, когда вы обращаетесь к q- ›i в вашем примере. Хотя я не уверен.   -  person Chris Beck    schedule 17.08.2015
comment
В любом случае старый объект исчез, а p указывает на старый объект, а не на новый. - вы делаете много явно неправильных утверждений, включая это ... было бы Лучше задавать вопросы, чтобы дезинформация не была неверно истолкована другими читателями как достоверная справочная информация.   -  person Tony Delroy    schedule 17.08.2015
comment
@TonyD Пожалуйста, докажите, что я ошибаюсь.   -  person curiousguy    schedule 17.08.2015
comment
@curiousguy: как ты думаешь, что делает мой ответ? Ты просто не слушаешь. Размещение-новое завершает время жизни старого объекта и запускает новый объект по тому же адресу памяти; указатель p остается действительным и остается указывающим на новый объект. Если вы замедлитесь, чтобы подумать, это должно быть интуитивно очевидно, а если нет, я могу лишь повторить совет Manu343726.   -  person Tony Delroy    schedule 17.08.2015
comment
@TonyD Я изменил вопрос, чтобы указать, что это моя личная интуиция.   -  person curiousguy    schedule 17.08.2015
comment
Я не знаю, уместно ли задавать вопросы не по теме, но теперь у меня много вопросов о том, как именно это работает. TonyD, когда вы говорите, что новое размещение завершает старое время жизни, означает ли это, что у исходного объекта вызывается деструктор, или это пропускается? Или не определено, что произойдет, если у объекта есть dtor. (Собираюсь провести несколько экспериментов / читать сейчас)   -  person Chris Beck    schedule 17.08.2015
comment
@ChrisBeck Если вы не вызовете деструктор, он не будет вызван.   -  person curiousguy    schedule 17.08.2015
comment
@ChrisBeck: деструктор исходного объекта не вызывается ... который не имеет неопределенного поведения, пока остальная часть программы не зависит от побочных эффектов деструктора. См. 3.8 / 1 Время жизни объекта типа T заканчивается, когда: ... хранилище, которое занимает объект, повторно используется или освобождается, а 3.8 / 4 - любая программа, которая зависит от стороны эффекты, производимые деструктором, имеют неопределенное поведение.   -  person Tony Delroy    schedule 17.08.2015
comment
@TonyD любая программа, которая зависит от побочных эффектов, производимых деструктором, имеет неопределенное поведение, возможно, одно из менее внятных предложений стандарта ...   -  person curiousguy    schedule 17.08.2015
comment
@curiousguy: тоже излишество. Я думаю, что возможность повторного использования памяти заключается в том, чтобы узаконить эффективный отказ от старого содержимого пула памяти, экономя время на итерацию и вызов деструкторов, когда объекты больше не имеют значения. Должен быть какой-то хороший способ сформулировать требование для удовлетворения естественных последствий реализации - что неразрушенные перезаписанные объекты просто не имеют отношения к остальной части выполнения.   -  person Tony Delroy    schedule 17.08.2015
comment
@juanchopanza инициализирован Я не уверен, что означает это слово.   -  person curiousguy    schedule 18.08.2015


Ответы (2)


(создание вики-сообщества как включения комментария dyp re 3.8 / 7 очень важно; хотя мой предыдущий анализ был правильным, я бы сказал примерно то же самое о коде, который был сломан, не обращая внимания на 3.8 / 7 сам)

Const *q,*p = new Const(1);
new (p) Const(2);

Строка new(p) Const(2); перезаписывает объект, созданный с помощью Const(1).

memcpy (&q, &p, sizeof p);

Это эквивалент q = p;.

cout << q->i;

Это обращается к члену q->i, который будет 2.

Несколько примечательных вещей:

  • std::memcpy - уродливый способ присвоить p _11 _... это законно, хотя в версиях 3.9 / 3:

Для любого тривиально копируемого типа T, если два указателя на T указывают на разные T объекты obj1 и obj2, где ни obj1, ни obj2 не являются подобъектами базового класса, если базовые байты (1.7), составляющие obj1, копируются в obj2, obj2 должен впоследствии будут иметь то же значение, что и obj1. [ Пример:

T* t1p;
T* t2p;
// provided that t2p points to an initialized object ...
std::memcpy(t1p, t2p, sizeof(T));
// at this point, every subobject of trivially copyable type in *t1p contains
// the same value as the corresponding subobject in *t2p
  • Замена старого Const(1) объекта на Const(2) разрешена, если программа не зависит от побочных эффектов первого деструктора, а это не так.

  • (как dyp отмечено в комментариях ниже) постоянный доступ к объекту Const(2) с использованием p является незаконным согласно третьему пункту 3.8 / 7:

указатель, указывающий на исходный объект [...], может использоваться для управления новым объектом, если ...

  • тип исходного объекта не соответствует const и, если это тип класса, не содержит каких-либо нестатических элементов данных, чей тип соответствует const или ссылочному типу ...
  • использование q, а не p для доступа к i, по-видимому, необходимо, чтобы избежать оптимизации компилятора, основанной на предполагаемом знании i.

Что касается вашего комментария:

Обратите внимание, что после создания второго Const, p семантически (намеренно?) Не указывает на новый объект, а первый отсутствует, поэтому его можно использовать как void*.

Учитывая, что вы разместили новый объект по адресу, содержащемуся в p, p наверняка указывает на вновь созданный объект, и очень намеренно, но его нельзя использовать для управления этим объектом в версии 3.8 / 7, как указано выше.

Учитывая, что у вас, кажется, есть понятие семантического указания, которое не определено в C ++, истинность этой части утверждения находится в вашем собственном сознании.

'после создания второго Const, _40 _... можно использовать как void*' не имеет смысла ... он не более удобен, чем что-либо прежде.

Но второй объект создается по тому же адресу, поэтому битовая комбинация p представляет адрес нового объекта.

Конечно, но ваши комментарии показывают, что вы думаете, что битовый шаблон каким-то образом отличается от значения указателя применительно к присваиванию с =, что неверно.

new (p) Const(2) стереть старый объект, хранящийся в p, поэтому указатель больше не действителен, кроме как указатель на хранилище (void*).

стирание - странный термин для этого ... перезапись была бы более значимой. Как dyp отмечалось и объяснялось выше, 3.8 / 7 говорит, что вы не должны манипулировать объектом, на который указывает p после размещения new, но значение и тип указателя не зависят от placmeent new. Подобно тому, как вы можете вызвать f(void*) с указателем на любой тип, объект размещения new не должен знать или заботиться о типе выражения p.

После p->~Const() или memset (p, 0, sizeof *p) становится ясно, что p не указывает на действительный объект, поэтому p можно использовать только как указатель на хранилище (void* или char*), например, для восстановления другого объекта. В этот момент p->get0() не допускается.

По большей части это правда, если под p можно использовать только значение, вы имеете в виду значение p в то время, а не сам указатель (который, конечно, также может быть назначен). И вы пытаетесь быть немного умнее с этим void* / char* - p остается Const*, даже если он используется только при размещении new, которое не заботится о типе указателя.

Я хочу восстановить значение p как Const*.

Значение p не изменилось после его первой инициализации. Размещение-new использует значение, но не изменяет его. Восстанавливать нечего, ничего не потеряно. Тем не менее, dyp подчеркнула необходимость не использовать p для управления объектом, поэтому, хотя значение не было потеряно, его также нельзя использовать напрямую, как хотелось бы.

person Community    schedule 17.08.2015
comment
Битовый шаблон указателя никогда не является недопустимым. Таким образом, битовая комбинация - это не то же самое, что значение. Вы можете скопировать битовый шаблон недействительного указателя. - person curiousguy; 17.08.2015
comment
@curiousguy: p был правильно инициализирован, поэтому он имеет значение, к которому можно безопасно получить доступ и скопировать с помощью =. Если p были неинициализированы или намеренно повреждены, тогда различие становится актуальным, и возможно, что memcpy будет безопасным, а = - нет. И, кстати, вы можете копировать значения недействительных указателей ... Стандарт считает недействительным указатель, который не на действующий в настоящее время объект, он отличается от битового шаблона, который даже не ссылается на законный адрес. - person Tony Delroy; 17.08.2015
comment
Битовый шаблон (представление объекта) содержит значение (представление значения) (§3.9 / 4) для тривиально копируемых типов, которыми является указатель. - person Daniel Jour; 17.08.2015
comment
Значит, когда вы уничтожаете объект, указатели на этот объект все еще действительны? - person curiousguy; 17.08.2015
comment
Кроме того, вы говорите, что я могу легально изменить переменную const int? - person curiousguy; 17.08.2015
comment
@curiousguy: указатель p указывает не на Const(1) в каком-то мистическом смысле - это на память, в которой позже создается Const(1) was constructed, which is the same memory where Const (2) `. Указатель p остается пригодным для использования на всем протяжении. - person Tony Delroy; 17.08.2015
comment
@curiousguy Действительны ли указатели на объект после его уничтожения, зависит от того, что именно вы подразумеваете под допустимым. Они будут недопустимыми для разыменования при попытке доступа к исходному объекту, но они могут стать действительными при создании нового объекта в том же месте. Здесь происходит не изменение переменной const int. Вы заменяете переменную другой, и это, безусловно, законно. (Например, a=b; может это сделать, если, скажем, a::some_const_variable имеет значение, отличное от b::some_const_variable.) - person David Schwartz; 17.08.2015
comment
@DavidSchwartz Тогда я также могу изменить динамический тип объекта. : D - person curiousguy; 17.08.2015
comment
@TonyD Если указатели не являются мистическими, тогда разрешен доступ за пределами массива, см. stackoverflow.com/questions/32043795/ - person curiousguy; 17.08.2015
comment
Если я delete указатель, вызываю new, и случайно new дает мне то же значение, могу ли я использовать удаленный указатель? - person curiousguy; 17.08.2015
comment
Что касается замены переменной на другую, о которой упоминал Дэвид Шварц, вы можете увидеть это в действии как часть ответа, который я написал не так давно: stackoverflow.com/a/31939212/1116364 Существует разница между концептуальной константностью и невозможностью изменить место в памяти. - person Daniel Jour; 17.08.2015
comment
@DavidSchwartz и cc Тони Д.: В стандарте явно указано, при каких условиях вы можете использовать указатель на объект, хранилище которого было повторно использовано для создания другого объекта. В случае OP исходный объект содержит член данных const, и, следовательно, [basic.life] p7.3 говорит, что указатель не указывает на новый объект после размещения-new. - person dyp; 17.08.2015
comment
@dyp: код в вопросе копирует p в q, поэтому он не манипулирует [ing] новым объектом с помощью старого указателя p, а обращается к нему с помощью вновь назначенного q с определенным поведением. Это действительно очень интересно. Я обновлю свой ответ, чтобы выделить это - спасибо. - person Tony Delroy; 17.08.2015
comment
Я не совсем уверен, что перезапись области памяти (где объект = область памяти + X) не считается манипуляцией, но ИМО, семантика времени жизни + memcpy в любом случае недооценена. Однако указатель даже не ссылается автоматически на новый объект в соответствии с этим абзацем. - person dyp; 17.08.2015
comment
@dyp: английский так легко неоднозначен - для меня указатель, указывающий на исходный объект ... автоматически будет ссылаться на новый объект и, как только время жизни нового объекта начнется, может использоваться для управления новый объект, если ... звучит так, будто бит автоматической ссылки является безусловным, но возможность манипулирования условна. Тем не менее, если людям так легко интерпретировать по-разному, мнения авторов компилятора тоже могут отличаться, и было бы разумнее - по крайней мере - сделать член не-const (еще лучше, чтобы прекратить взламывать дрянной код). - person Tony Delroy; 17.08.2015
comment
И перезапись происходит со старым объектом - это манипулирование новым объектом, которое потенциально проблематично, так как это происходит через значение указателя, надежность которого мы обсуждаем ... - person Tony Delroy; 17.08.2015
comment
@TonyD Требования маркированного списка также охватывают случаи, когда у вас есть указатель на подобъект, вы уничтожаете весь объект, частью которого был этот подобъект, а затем создаете совершенно новый полный объект без этого подобъекта (и другой компоновки памяти). Например. struct foo { int x; short y; }; struct bar { short x; float y; }; foo f; auto* px = &f.x; new(&f) bar; - person dyp; 17.08.2015
comment
@dyp: да - еще одно разумное исключение - какая-то конкретная причина, по которой вы упоминаете это в контексте этого вопроса, или просто находите это интересным ...? - person Tony Delroy; 17.08.2015
comment
Извините, это был плохой пример. struct foo { short x; short y; }; foo f; auto* p = &f.y; auto* q = new((void*)&f) int(42);, тогда p не будет указывать на какой-либо объект, особенно на *q (который является новым объектом). Я думаю, что пункты списка имеют намерение сказать это, но они говорят это только в том случае, если автоматически ссылаться действительно является условным. - person dyp; 17.08.2015
comment
@dyp: Требования к маркированному списку также охватывают случаи, когда у вас есть указатель на подобъект - на самом деле, я не могу обнаружить это в n3797 или n3690 ... в них 3.8 / 7 запускается, если ... новый объект создается в месте хранения, которое занимал исходный объект * - в приведенном выше коде вы создаете новый объект в &f, поэтому исходный объект, о котором идет речь, foo, и продолжает указатель, указывающий на исходный объект - значит, мы говорим об указателях на &foo; Я бы рискнул исключить p, поэтому остальная часть абзаца не применяется (ни ссылаться, ни манипулировать). - person Tony Delroy; 17.08.2015
comment
@dyp p по-прежнему относится к какому-то (зомби) short объекту, который лежит там, ничего не делая. - person curiousguy; 17.08.2015
comment
семантически указывающий просто означает, что указатель может быть разыменован, и вы получите объект, на который он семантически указывает - person curiousguy; 18.08.2015
comment
@curiousguy: если вы хотите использовать терминологию, используемую в стандарте C ++ или в общем сообществе специалистов по вычислительной технике, это все хорошо, и обсуждение будет иметь значение, за которым другие читатели могут легко последовать. Но мне просто неинтересно обсуждать C ++ с использованием придуманной вами терминологии - это означает, что другие не могут следовать за вами, не прыгая по этим комментариям, сообщениям и ответам и перечитывая все подряд, когда они натыкаются на ретроспективные определения ваших терминов. - person Tony Delroy; 19.08.2015
comment
@TonyD Итак, расскажите мне, что такое терминология CS и std C ++. - person curiousguy; 19.08.2015
comment
@curiousguy Я не комментирую, есть ли термин, который соответствует вашему точному понятию, просто разбрасывание терминов так, как будто они будут означать то же самое для других, создает еще большую путаницу. Если вы не можете его найти и имеете в виду указатель, который можно разыменовать, чтобы получить доступ к объекту, адрес которого он содержит, скажите это. - person Tony Delroy; 19.08.2015
comment
@TonyDelroy Я думаю, что std C ++ бросает вызов терминам, говоря, что значение ptr полностью определяется его битовым шаблоном, и они пытаются сказать, что некоторые указатели допустимы в каком-то контексте, а другие нет, когда они имеют одинаковое значение. Это понятие значения, при котором значение переменной не определяет допустимые операции. - person curiousguy; 07.06.2018
comment
TonyDelroy: в каком-то мистическом смысле если вы хотите использовать терминологию, используемую в стандарте C ++ или общем сообществе компьютерных наук, это все хорошо смешно - person curiousguy; 19.12.2018

Это предназначено только как дополнение к ответу @Tony D относительно

new (p) Const(2) стереть старый объект, хранящийся в p

Я думаю, вам нужно различать объект и концептуальную идею «экземпляра».

[...] Объект - это область хранения. [...]

[N4431 §1.8/1]

Таким образом, указатель p указывает на область хранения, которая содержит битовую комбинацию некоторого "экземпляра" перед размещением нового и некоторого другого битового шаблона другого, но хорошо сконструированного "экземпляра" правильного (того же) типа.

Таким образом, в месте, на которое указывает p, есть действительный объект, и при назначении из него q q указывает на него. Хотя, как отмечено в другом ответе, доступ к нему через p не разрешен.

person Daniel Jour    schedule 17.08.2015
comment
Значит, это должно сработать, потому что я не менял тип? - person curiousguy; 17.08.2015
comment
Думаю, да. Если вы конструируете объект другого типа, то UB будет обращаться к нему, используя неверно введенный указатель. - person Daniel Jour; 17.08.2015
comment
Так вы согласны с тем, что const int принимает разные значения? - person curiousguy; 18.08.2015
comment
const int не меняет значения. Это два разных const int, которые оказались в одном месте в памяти (в разное время). - person Daniel Jour; 19.08.2015
comment
С точки зрения указателя, это все еще изменяющаяся константа. - person curiousguy; 19.08.2015
comment
Какой указатель? Оба не являются константными, поэтому они могут изменять то, на что указывают. - person Daniel Jour; 19.08.2015
comment
В этом суть: q изменен в моем коде, поэтому возникает проблема с константностью, а p не используется после модификации. Мое возражение не относится к моему коду, оно относится к вашему коду. - person curiousguy; 19.08.2015
comment
Какой именно код? В вашем коде нет проблем. Вы изменяете q, и это нормально, поскольку он не const. - person Daniel Jour; 19.08.2015
comment
Позвольте нам продолжить это обсуждение в чате. - person curiousguy; 19.08.2015