2009-09-11 3 views
4

Я слышал несколько слов о предостережении от доставки объекта в другое место памяти через memcpy, но я не знаю конкретных причин. Если его содержащиеся члены не имеют сложных вещей, которые зависят от местоположения памяти, это должно быть абсолютно безопасным ... или нет?Безопасное перемещение объекта C++

EDIT: предполагается случай использования представляет собой структуру данных, как vector, в котором хранятся объекты (не указатели к объектам) в непрерывном куске памяти (т.е. массив). Чтобы вставить новый объект на n-й позиции, все объекты, начиная с позиции n и далее, должны быть перемещены, чтобы освободить место для объекта, который нужно вставить.

+1

Я когда-то реализовал контейнер (http://www.spotep.com/dev/devector.h), где я думал об одном и том же, и все же я не нашел один класс, который не мог быть «перемещен». Единственные случаи, о которых я мог подумать, - это если какой-то класс регистрирует свое собственное местоположение памяти каким-то странным образом или состоит из указателя, указывающего на себя. Я слышал, что существуют реализаторы std :: string, которые могут указывать либо на себя, либо на кучу, но я действительно не видел его. –

+1

Список с двойной памятью с двойной памятью будет разваливаться, если он переместится. Трюк XOR-ing указателя в узле с адресом узла, из которого вы пришли, не будет работать. – Novelocrat

+0

@Victor: В сценарии MI с виртуальным базовым классом это может завершиться неудачно, если среда выполнения хранит ссылку на виртуальную базу как указатель вместо смещения. – sbi

ответ

3

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

В основах кода, над которыми я работаю, большинство объектов побитообразно перемещаются, так как они не хранят ссылки на себя. Однако некоторые структуры данных не являются побитовыми (я считаю, что std :: set gcc не был побитовым, другие примеры были бы linked list nodes). В общем, я бы избегал попытки использовать это свойство, поскольку это может привести к очень тяжелому отладке ошибок и предпочтет объектно-ориентированные вызовы.

Edited добавить:

Там, кажется, некоторая путаница о том, как/почему кто-то хотел бы сделать это: вот комментарий, который я сделал на том, как:

Обычно, я вижу выше на альтернативных реализации вектора. Память выделяется через malloc (sizeof (Class) * size), а объекты построены на месте через явно называемые конструкторы и деструкторы. Иногда (например, при перераспределения), они должны быть перемещены, поэтому вариант сделать повторного призыв StD :: вектора по конструкторам копирования на новой памяти и деструкторы на старые, или использовать memcopy и просто «бесплатно» старый блок. В большинстве случаев последний просто «работает», но не для всех объектов.

Что касается причин, то подход memcopy (или realloc) может быть значительно быстрее.

Да, оно вызывает неопределенное поведение, но оно также просто работает для большинства объектов. Некоторые считают, что скорость стоит того. Если бы вы были действительно настроены на использование этого подхода, я бы предложил реализовать свойство bitwise_movable типа, чтобы разрешить типы, которые работают для белого, и вернуться к традиционной копии для объектов, не входящих в белый список, подобно примеру here.

1

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

В чем проблема с конструктором копирования и операторами присваивания?

6

Одна из основных причин, почему вы не должны это делать, - это деструкторы. Когда вы memcpy объект C++ в другое место в памяти, вы получите 2 версии объекта в памяти, для которых был запущен только один конструктор. Это приведет к уничтожению логики освобождения ресурсов почти в каждом классе C++.

+0

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

+1

@Jen, если вы не сделаете что-то по-настоящему злое в предыдущее место в памяти для старого объекта, который будет запускать деструктор. – JaredPar

+0

Как? Единственный способ вызова деструктора - через указатель. Если никакие указатели не ссылаются на ячейку памяти, это просто куча бит. Или я чего-то не хватает? –

2

Если у объекта не было указателей внутри него и нет виртуальных функций, нет детей с кем-либо из них, вы можете с ним справиться. Это не рекомендуется !!!

Это должно быть сделано с использованием функции копирования или глубокой печати или переопределенных операторов.

В этом методе вы можете вызвать нового конструктора и скопировать его элементы данных по одному.

Для мелкой копии вы должны копировать указатели/ссылки, чтобы у вас было два объекта, указывающих на те же содержащиеся элементы .... потенциальный кошмар утечки памяти.

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

Чтобы переместить объект, вы должны скопировать его и удалить оригинал.

4

Это запрещено по языковой спецификации. Это неопределенное поведение. То есть, в конечном счете, что с этим не так. На практике он имеет тенденцию возиться с вызовами виртуальных функций, а это означает, что деструктор будет выполняться дважды (и чаще, чем конструкторы), объекты-члены мелко копируются (так, если, например, если вы попробуете этот трюк с std::vector , он взрывается, так как несколько объектов в конечном итоге указывают на тот же внутренний массив.)

Исключение составляют типы POD. У них нет (копировать) конструкторы, деструкторы, виртуальные функции, базовые классы или что-то еще, что может привести к его разрыву, поэтому с ними вы можете использовать memcpy для их копирования.

2

Короткий ответ: std::memcpy() предназначен для перемещения память, а не для движущихся объектов. Использование этого, тем не менее, вызовет неопределенное поведение.

Несколько дольше ответ: объект C++, который не является POD, может содержать ресурсы, которые необходимо освободить и которые хранятся в ручках, которые не могут быть легко скопированы. (Популярным ресурсом является память, где дескриптор - указатель.) Он также может содержать материал, вставленный в реализацию (указатели экземпляра виртуального базового класса), которые нельзя копировать, как если бы это была память.

Единственный верный способ перемещения объекта в C++ 98 и C++ 03 - это скопировать его в новое место и вызвать деструктор в старом. (В C++ 1x будет перемещение семантики, поэтому в некоторых случаях ситуация может стать более интересной.)

+0

Перемещение семантики в C++ 1x не будет иметь никакого отношения к этому. Это касается случаев предотвращения создания/уничтожения временных рядов. Возьмите этот вызов foo ("Hello") на эту функцию foo (const std :: string & s). Перемещение семантики уменьшит необходимость создания временного s, если существует функция foo (const std :: string && s). – jmucchiello

+0

@jmucchiello: вот почему я квалифицировал свое заявление «в определенных случаях». (Кстати, я не думаю, что ваш пример прав. В обоих случаях необходимо создать временное создание). – sbi

0

В целом (и на всех языках, а не только на C++), чтобы безопасно перемещать объект, вам также необходимо переписать ВСЕ указатели/ссылки на этот объект, чтобы указать на новое местоположение. Это проблема в C++, потому что нет простого способа определить, есть ли у какого-либо объекта в системе «скрытый» указатель на объект, который вы перемещаете. Как вы заметили, некоторые классы могут содержать скрытые указатели на себя. Другие классы могут иметь скрытые указатели на фабричном объекте, который отслеживает все экземпляры. Также возможно для кажущихся несвязанных классов кэшировать указатели на объекты по разным причинам.

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

Смежные вопросы