Безопасное перемещение объекта C ++ - PullRequest
4 голосов
/ 11 сентября 2009

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

РЕДАКТИРОВАТЬ: Рассматриваемый вариант использования представляет собой структуру данных, подобную vector, которая хранит объекты (не указатели на объекты) в непрерывном фрагменте памяти (то есть массиве). Чтобы вставить новый объект в n -й позиции, все объекты, начиная с позиции n и далее, необходимо переместить, чтобы освободить место для вставляемого объекта.

Ответы [ 7 ]

6 голосов
/ 11 сентября 2009

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

4 голосов
/ 11 сентября 2009

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

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

3 голосов
/ 11 сентября 2009

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

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

Отредактировано для добавления :

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

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

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

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

2 голосов
/ 11 сентября 2009

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

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

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

2 голосов
/ 11 сентября 2009

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

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

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

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

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

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

1 голос
/ 11 сентября 2009

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

Что не так с конструктором копирования и операторами присваивания?

0 голосов
/ 11 сентября 2009

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

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

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