2016-01-31 3 views
6

В C у нас есть функции memcpy и memmove для эффективной копирования данных. Первое дает неопределенное поведение, если область источника и получателя перекрывается, но последнее гарантировано имеет дело с тем, что «как ожидалось», предположительно, заметив направление перекрытия и (при необходимости) выбрав другой алгоритм.Копирование классов между диапазонами, которые могут перекрываться

Вышеуказанные функции доступны в C++ (как std::memcpy и std::memmove), конечно, но они не работают с нетривиальными class es. Вместо этого мы получаем std::copy и std::copy_backward. Каждое из них работает, если диапазоны источника и назначения не перекрываются; кроме того, каждый гарантированно работает для одного «направления» перекрытия.

Что мы можем использовать, если мы хотим скопировать из одного региона в другой, и мы не знаем во время компиляции, если диапазоны могут пересекаться или в каком направлении может возникать совпадение? Кажется, у нас нет возможности. Для общего iterator s может быть трудно определить, перекрываются ли диапазоны, поэтому я понимаю, почему в этом случае не предусмотрено решение, но о том, когда мы имеем дело с указателями? В идеале, было бы функция, как:

template<class T> 
T * copy_either_direction(const T * inputBegin, const T * inputEnd, T * outputBegin) { 
    if ("outputBegin ∈ [inputBegin, inputEnd)") { 
     outputBegin += (inputEnd - inputBegin); 
     std::copy_backward(inputBegin, inputEnd, outputBegin); 
     return outputBegin; 
    } else { 
     return std::copy(inputBegin, inputEnd, outputBegin); 
    } 
} 

(Аналогичная функция T * заменена std::vector<T>::iterator бы неплохо еще лучше было бы, если бы это было гарантированно работать, если inputBegin == outputBegin, но это a separate gripe of mine.).

К сожалению, я не вижу разумного способа записать условие в инструкции if, поскольку сравнение указателей на отдельные блоки памяти часто приводит к неопределенному поведению. С другой стороны, реализация, очевидно, имеет свой собственный способ сделать это, потому что std::memmove по своей сути требует этого. Таким образом, любая реализация может обеспечить такую ​​функцию, тем самым заполняя потребность в том, что программист просто не может. Поскольку std::memmove считался полезным, почему бы не copy_either_direction? Есть ли решение, которое мне не хватает?

+1

Поскольку классы могут содержать не-POD типов, такие как 'станд :: string' или' станд :: VECTOR' , вы рассматриваете возможность глубокого перемещения? –

+0

Я рассматриваю возможность запуска любой функции копирования, которую использует класс. –

+1

Можете ли вы привести пример, где регион может пересекаться? Чтобы получить перекрывающиеся области, вам нужно будет сделать некоторые небезопасные типы. «Memmove» - это функция низкого уровня, касающаяся перемещения содержимого памяти без каких-либо конструкций высокого уровня. Алгоритмы более высокого уровня 'std :: copy' вызывают конструкторы, которые улучшают безопасность, но уменьшают эффективность. –

ответ

-1

Что мы можем использовать, если мы хотим скопировать из одного региона в другой, и мы не знаем во время компиляции, если диапазоны могут пересекаться или в каком направлении может возникать совпадение?

Это не логически последовательная концепция.

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

К переезд объект на самом деле также не логически последователен. Зачем? Потому что перемещение - это вымысел в C++; после переезда вы еще имеют два совершенно функциональных объекта. Операция перемещения - это просто разрушительная копия, которая крадет ресурсы, принадлежащие другому объекту. Это все еще существует, и это по-прежнему жизнеспособный объект.

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

Тривиально скопированные типы обходятся вокруг этого, потому что они всего лишь блоки бит, без деструкторов или специализированных операций копирования. Поэтому их жизнь не такая жесткая, как у других. Нетривиальный тип копирования не может это сделать, потому что:

Опыт работы с memmove предполагает, что в этом случае может быть решение (и, возможно, также итераторы в смежные контейнеры).

Это не является ни возможным, ни вообще желательным для типов, которые не являются тривиально скопируемыми в C++.

Правила для тривиальной скопируемости заключаются в том, что тип не имеет нетривиальных операторов-конструкторов копирования/перемещения/присваивания, а также нет нетривиального деструктора. Тривиальный конструктор/назначение copy/move - это не что иное, как memcpy, а тривиальный деструктор ничего не делает. И поэтому эти правила эффективно гарантируют, что тип является не более чем «блоком бит». И один «блок бит» ничем не отличается от другого, поэтому копирование его через memmove является правовой конструкцией.

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

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

И это даже не должно быть указателем или дескриптором файла. Возможно, каждый экземпляр класса имеет уникальный идентификатор; такое значение генерируется во время построения, и его никогда не копируется (новые копии получают новые значения). Чтобы вы нарушили такое ограничение с memmove, вы оставите свою программу в неопределенном состоянии, потому что у вас будет код, который ожидает, что такие идентификаторы будут уникальными.

Вот почему memmove IN для нетривируемых копируемых типов дает неопределенное поведение.

+1

Я думаю, что это не соответствует сути вопроса, он не просит буквально «memmove» объекта, скорее, он хочет использовать встроенный оператор присваивания и правильно перемещать объекты. –

+1

I _want_ to запустите функцию копирования 'class', используя ее для копирования элементов (по одному за раз) из одной серии адресов в другую. Я не могу использовать 'std :: memmove', поскольку, как вы заметили, он просто скопирует блок бит, который может и не быть тем, что требуется. Я не могу использовать 'std :: copy' или' std :: copy_backward', поскольку для каждого из них требуются определенные отношения между диапазонами источника и назначения, которые могут быть не гарантированы во время компиляции. –

+0

@JoshuaGreen: Я добавил дополнительную информацию, которая объясняет, почему то, что вы хотите, невозможно. –

0

memmove работает, потому что он передает указатели на смежные байты, поэтому диапазоны двух блоков, которые нужно скопировать, хорошо определены. copy и move принимают итераторы, которые не обязательно указывают на смежные диапазоны. Например, итератор списка может перемещаться в памяти; нет диапазона, на который может смотреть код, и никакого значимого понятия перекрытия.

+1

Я согласен в общем, но что, если 'iterator' на самом деле являются сырыми указателями (или, возможно, 'std :: vector :: iterator', для которых сравнения могут также быть разумными [для реализации])? –

+0

Предположим, что 'it1, it2, it3' являются итераторами в один и тот же' std :: list' Правильно ли сказать 'std :: copy (it1, it2, it3);'? (Если вы говорите «я не знаю», то вопрос OP: как мы это делаем правильно) –

+0

@ M.M, я не уверен, что там _can_ будет простым решением для 'std :: list's. Я думаю, что _should_ будет решением для массивов (и, возможно, 'std :: vector' и 'std :: array'). –

0

Я недавно узнал, что std::less специализирован для указателей таким образом, чтобы обеспечить общий порядок, по-видимому, позволяющий хранить указатели в std::set и связанных с ним class. Предполагая, что это должны согласиться со стандартным упорядочением, когда последняя определяется, я думаю, что следующий будет работать:

#include <functional> 

template<class T> 
T * copy_either_direction(const T * inputBegin, const T * inputEnd, T * outputBegin) { 
    if (std::less<const T *>()(inputBegin, outputBegin)) { 
     outputBegin += (inputEnd - inputBegin); 
     std::copy_backward(inputBegin, inputEnd, outputBegin); 
     return outputBegin; 
    } else { 
     return std::copy(inputBegin, inputEnd, outputBegin); 
    } 
}