Безопасен ли memcpy с этим указателем?

В настоящее время я пишу собственную реализацию строки на С++. (Только для тренировки).

Однако в настоящее время у меня есть этот конструктор копирования:

// "obj" has the same type of *this, it's just another string object
string_base<T>(const string_base<T> &obj)
        : len(obj.length()), cap(obj.capacity()) {
    raw_data = new T[cap];
    for (unsigned i = 0; i < cap; i++)
        raw_data[i] = obj.data()[i];
    raw_data[len] = 0x00;
}

и я хотел немного увеличить производительность. Так что я пришел к идее использовать memcpy(), чтобы просто скопировать obj в *this.

Просто так:

// "obj" has the same type of *this, it's just another string object
string_base<T>(const string_base<T> &obj) {
     memcpy(this, &obj, sizeof(string_base<T>));
}

Безопасно ли перезаписывать данные *this таким образом? Или это может вызвать какие-то проблемы?

Заранее спасибо!


person cocoz1    schedule 18.06.2018    source источник
comment
Этот поток делает для интересного чтения; почти дубликат: stackoverflow.com /вопросы/30114397/   -  person Bathsheba    schedule 18.06.2018
comment
вы также можете использовать strcpy, который останавливается на символе NULL. Он может отличаться с точки зрения производительности по сравнению с memcpy.   -  person MauriceRandomNumber    schedule 18.06.2018
comment
Еще одна вещь, которую следует учитывать, это то, что современные компиляторы часто достаточно умны, чтобы распознать цикл в первом примере и заменить его на memcpy, когда это необходимо. Так что это может быть преждевременной оптимизацией.   -  person Bo Persson    schedule 18.06.2018
comment
Кстати, вам просто нужно скопировать len объектов, а не cap.   -  person Jarod42    schedule 18.06.2018


Ответы (4)


Нет, это не безопасно. С cppreference.com:

Если объекты не TriviallyCopyable, поведение memcpy не указано и может быть неопределенным.

Ваш класс не TriviallyCopyable, так как его конструктор копирования предоставляется пользователем.


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

person Daniel Langr    schedule 18.06.2018
comment
Что означает TriviallyCopyable? - person cocoz1; 18.06.2018
comment
Я не уверен, что достаточно простого копирования, хотя это необходимо. - person Bathsheba; 18.06.2018
comment
@codekaizer Вовсе нет: std::cout << std::is_trivially_copyable<std::string>::value << std::endl;. (Опять же, std::string имеет определяемый пользователем конструктор копирования.) - person Daniel Langr; 18.06.2018
comment
@DanielLangr, да, только что прочитал этот ответ. На основе комментария - std::string не может быть легко скопирован в С++ 14 и далее - person Joseph D.; 18.06.2018
comment
@codekaizer Нет, прочитайте следующий комментарий: Объект, содержащий std::string, никогда не мог быть легко скопирован. - person Daniel Langr; 18.06.2018

Это будет создавать проблемы. Все ссылки и указатели будут просто скопированы, даже указатель на raw_data, который будет таким же, как исходный объект.

Чтобы использовать memcpy, ваш класс должен:

  • Быть просто копируемым
  • Не иметь ссылок или указателей, если только: статические указатели или не владеют указанными данными. Если только вы не знаете, что делаете, например реализуете механизм копирования при записи или когда это поведение предназначено и управляется иным образом.
person Attersson    schedule 18.06.2018
comment
С указателями все в порядке, если они не владеют данными, на которые указывают. В противном случае мы получаем многократное удаление одной и той же памяти. - person Aconcagua; 18.06.2018
comment
Точно... что сказал @Aconcagua. Отредактировано. - person Attersson; 18.06.2018
comment
Конечно, они также могут быть подсчитаны. Что касается механизма копирования при записи. Вот почему я был бы осторожен с таким сильным заявлением. - person Daniel Langr; 18.06.2018
comment
Ну, я снова отредактировал, но это должно быть так. Предупреждение о ссылках и указателях обязательно - person Attersson; 18.06.2018
comment
Если у вас есть ссылочные элементы, и вы делаете memcpy, это означает, что вы перепривязываете ссылку. Это незаконно. - person curiousguy; 18.06.2018
comment
Как вы думаете, я должен поставить запятую? Нет ссылок (или указателей, если только...)? - person Attersson; 18.06.2018

Как уже говорили другие, для правильной работы memcpy копируемые объекты должны быть легко скопированы. Для произвольного типа, такого как T в шаблоне, вы не можете быть в этом уверены. Конечно, вы можете это проверить, но гораздо проще поручить это кому-то другому. Вместо того, чтобы писать этот цикл и настраивать его, используйте std::copy_n. Он будет использовать memcpy, когда это уместно, и поэлементное копирование, когда это не так. Так изменить

raw_data = new T[cap];
for (unsigned i = 0; i < cap; i++)
    raw_data[i] = obj.data()[i];

to

raw_data = new T[cap];
std::copy_n(obj.data(), cap, raw_data);

Это также имеет небольшое преимущество, поскольку не вычисляет obj.data() при каждом проходе цикла, что является оптимизацией, которую ваш компилятор может применить, а может и не применить.

person Pete Becker    schedule 18.06.2018

Из ограниченного фрагмента кажется довольно очевидным, что raw_data является членом и указателем на массив new[] из T. Если вы запоминаете объект, вы запоминаете и этот указатель. Вы не копируете массив.

Посмотрите на свой деструктор. Вероятно, он безоговорочно вызывает delete[]. Он понятия не имеет, сколько копий существует. Это означает, что он вызывает delete[] слишком часто. Это поправимо: вам понадобится что-то похожее на shared_ptr. Это совсем не тривиально; вам нужно беспокоиться о безопасности потоков для этого количества общих ресурсов. И, очевидно, вы не можете просто memcpy объект, так как это не обновит количество акций.

person MSalters    schedule 18.06.2018