Если я использую функцию memcpy для копирования массива, какие могут быть последствия: узкие места и изменения производительности?

Я пытаюсь реализовать свой контейнер Vector в образовательных целях.
При реализации его резервного метода я столкнулся с вопросом:
это лучший способ перемещения массива элементов из одного места в памяти (исходной емкости) в память, выделенную под новую емкость?
Я рассматриваю 2 возможности: с помощью цикла или с помощью функции c memcpy.
Вот моя реализация:

template <typename T>
void MyVector<T>::reserve(int elements) 
{
    if (m_size >= elements) // when no need to reserve memory as at least demanded amount of memory has been already allocated
        return;
    m_capacity = elements;
    tmp = m_array;
    // allocate needed amout of memory:
    m_array = new T[elements];
    // copy elements ???? can I use memcpy????
    for (int i = 0; i < m_size; ++i)
    {
        m_array[i] = tmp[i];
    }
    delete tmp;
}

template <typename T>
void MyVector<T>::reserve1(int elements) 
{
    if (m_size >= elements) // when no need to reserve memory as at least demanded amount of memory has been already allocated
        return;
    m_capacity = elements;
    tmp = m_array;
    // allocate needed amout of memory:
    m_array = new T[elements];
    // copy elements ???? can I use memcpy????
    memcpy(m_array, tmp, m_size);
    delete [] tmp;
}

Вопросы: - какие узкие места следует учитывать?
- какая разница в производительности?
- есть ли более эффективные методы?
- не могли бы вы посоветовать мне источник информации, где реализация контейнеров STL подробно описана (моя цель - сделать свою собственную реализацию и сравнить с профессиональной реализацией, чтобы проверить свои знания и выяснить, какие области нуждаются в улучшении)


person spin_eight    schedule 21.03.2013    source источник
comment
Просто чтобы указать, должно быть delete[], а не delete.   -  person hmjd    schedule 22.03.2013
comment
вы можете использовать std::copy вместо memcpy   -  person Stephane Rolland    schedule 22.03.2013
comment
Можете ли вы посоветовать мне источник информации, где подробно описана реализация контейнеров STL ‹- Просто взгляните на фактический исходный код?   -  person us2012    schedule 22.03.2013
comment
@hmjd спасибо, я внес изменения.   -  person spin_eight    schedule 22.03.2013
comment
@Stephane Rolland, и если я использую std::copy, эти преимущества я получу по сравнению с моими методами ???   -  person spin_eight    schedule 22.03.2013
comment
@us2012 us2012 спасибо, я только что понял, что могу посмотреть реализацию в заголовке вектора, потому что это шаблон. Я помнил, что весь исходный код из STL скрыт от клиента.   -  person spin_eight    schedule 22.03.2013
comment
Если речь идет о производительности, вы должны ее измерить. Используйте профилировщик, как очень сонный.   -  person andre    schedule 22.03.2013
comment
Здесь была бы полезна семантика перемещения С++ 11...   -  person Roddy    schedule 22.03.2013
comment
@spin_eight преимущество настоящего C++, а не паршивого C :-) Мне нравится C, но вам следует избегать его, когда есть способы делать такие вещи на чистом C++. Профиль перед оптимизацией. Хотя у вас есть право задаться вопросом о memcpy... это тоже верно.   -  person Stephane Rolland    schedule 22.03.2013
comment
@andre большое спасибо за профилировщик, раньше я вообще не представлял, как оценить производительность моего приложения, нет, у меня есть хотя бы один инструмент   -  person spin_eight    schedule 22.03.2013


Ответы (3)


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

Рассмотрим вектор std::string : каждый из них обычно реализуется как небольшой объект, содержащий указатель на динамически выделяемый массив символов. Если вы memcpy строите, вы делаете копию указателей, но не их фактических символьных данных. Но владение этими указателями не передается. Поэтому, когда вы удаляете исходный массив (который вызывает деструкторы для всех этих строк), их память освобождается, и все ваши «новые» объекты имеют оборванные указатели.

Использование std::copy — лучший способ добиться этого.

person Roddy    schedule 21.03.2013
comment
Итак, если посмотреть на ваш пример: контейнер строк, он показывает, что мой метод не работает при работе с объектами, которые имеют указатели/ссылки в качестве членов. Причина в том, что memcopy позволяет выполнять поверхностную, а не глубокую копию. Выполняет ли std::copy глубокое копирование и как это делается (посредством вызова конструктора копирования или конструктора перемещения копирования)? Поскольку эта версия стандарта С++ std::copy доступна. Если сравнить цикл и std::copy, что более эффективно (правильно использовать)? - person spin_eight; 22.03.2013
comment
@spin_eight : std::copy вызывает оператор присваивания, а не конструктор копирования. Ваши объекты уже созданы new[]. Самый быстрый способ сделать это? Не думаю, что есть большая разница между вашим циклом и std::copy. std::vector будет намного быстрее для сложных объектов в C++11. - person Roddy; 22.03.2013
comment
std::copy может (если реализация решит это сделать) разрешить вызов memcpy (или что-то подобное) для тривиальных (POD) объектов. Так что во многих местах это также правильный выбор с точки зрения производительности. - person Tomek; 22.03.2013
comment
@spin_eight: Вы ставите (правильное использование) после более эффективного - большая ошибка. Эффективность — это хорошо, но ваша главная задача — работающий, чистый код. Никто не заботится о том, насколько эффективен ваш код, пока он не заработает. И большинство проблем с эффективностью решаются сами собой, если вы просто кодируете чистоту. Перестаньте беспокоиться об этом. - person GManNickG; 22.03.2013
comment
@GManNickG Я согласен с вами, в проекте, подходящем для использования, может быть более высокий приоритет, чем эффективность (особенно в C ++), поэтому это может позволить потратить больше времени и уделить больше внимания проблемной области (правильный дизайн ...). Но моя цель состояла в том, чтобы тренировать свой мозг, пытаясь реализовать что-то эффективное, а затем сравнить результат с уже реализованными вещами, поэтому я сделал это только в образовательных целях. - person spin_eight; 22.03.2013

Существуют аспекты производительности, а также другие факторы, при которых копирование объекта с использованием метода memcpy и «простого цикла» будет иметь значение. Например:

class Blah
{
    Blah()
    {
        some_function(this);
    }
};

MyVector<Blah> v;

... 

Теперь, если some_function хранит this каким-то образом [по какой-либо хорошей или плохой причине], ваш reserve1 приведет к его удалению, но ссылка на this останется, указывающая на удаленный объект. Что, вероятно, не то, что вы хотели.

То же самое, конечно, применяется, если объект содержит какой-либо объект, который использует распределение внутри, например std::string.

person Mats Petersson    schedule 21.03.2013

Я согласен с Родди. Но еще хуже. Даже если вам удастся освободить старую память без вызова деструктора, а другая память не будет освобождена, какой-то объект может содержать ссылки или указатели на внутренние части, которые все равно будут сломаны. Наконец, было бы неплохо узнать последствия нового выравнивания памяти. (Для T = char memcpy будет, вероятно, лучшим).

Если это сработает, принудительное назначение перемещения в вашем for может быть лучшим решением. (вам не нужны старые копии)

Что ж, в конечном итоге вы можете увидеть, как спроектирован std::vector, с отдельным выделением памяти, неинициализированным, а затем инициализированным, скопированным или перемещенным на месте. Взгляните на 19.4 Распределители в The C++ Programming Language, Third Edition by Stroustrup.

person qPCR4vir    schedule 21.03.2013
comment
Да, спасибо, я получил хороший ответ, который решил почти все мои проблемы. Поскольку я не собираюсь использовать MyVector только для встроенных типов данных, я понял, что memcpy будет плохой идеей. - person spin_eight; 22.03.2013