Могу ли я использовать memcpy в C++ для копирования классов, не имеющих указателей или виртуальных функций

скажем у меня есть класс, что-то вроде следующего;

class MyClass
{
public:
  MyClass();
  int a,b,c;
  double x,y,z;
};

#define  PageSize 1000000

MyClass Array1[PageSize],Array2[PageSize];

Если мой класс не имеет указателей или виртуальных методов, безопасно ли использовать следующее?

memcpy(Array1,Array2,PageSize*sizeof(MyClass));

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

Edit:

согласно комментариям sharptooths, данные не включают в себя какие-либо дескрипторы или аналогичную справочную информацию.

согласно комментарию пола R, я профилировал код, и в этом случае избежать конструктора копирования примерно в 4,5 раза быстрее. Отчасти причина здесь в том, что мой класс templated array несколько сложнее, чем упрощенный приведен пример и вызывает размещение " new " при выделении памяти для типов, которые не допускают мелкого копирования. Это фактически означает, что вызывается конструктор по умолчанию, а также конструктор копирования.

вторая правка

возможно, стоит отметить, что я полностью согласен с тем, что использование memcpy таким образом является плохой практикой и следует избегать в общих случаях. Конкретный случай, в котором он используется, является частью высокопроизводительного шаблона класс array, который включает параметр 'AllowShallowCopying', который будет вызывать memcpy, а не конструктор копирования. Это имеет большие последствия для производительности таких операций, как удаление элемента вблизи начала массива и подкачка данных во вторичное хранилище и из него. Лучшим теоретическим решением было бы преобразовать класс в простую структуру, но, учитывая, что это включает в себя много рефакторинга большой базы кода, избежать этого не то, что я хочу сделать.

10 ответов


позвольте мне дать вам эмпирический ответ: в нашем приложении в реальном времени мы делаем это все время, и он работает просто отлично. Это относится к MSVC для Wintel и PowerPC и GCC для Linux и Mac, даже для классов, имеющих конструкторы.

Я не могу процитировать главу и стих стандарта C++ для этого, просто экспериментальные данные.


согласно стандарту, если программист не предоставляет конструктор копирования для класса, компилятор синтезирует конструктор, который показывает инициализация memberwise по умолчанию. (12.8.8) однако в 12.8.1 стандарт также гласит:

объект класса может быть скопирован в двумя способы, с помощью инициализации (12.1, 8.5), в том числе для аргумента функции прохождение (5.2.2) и значение функции возвращение (6.6.3), и по назначению (5.17). Концептуально эти два операции выполняются копией назначение конструктор (12.1) и копия оператор (13.5.3).

ключевое слово здесь - "концептуально", что, согласно Липпман дает конструкторам компилятора " выход "для фактического выполнения инициализации memberwise в" тривиальных " (12.8.6) неявно определенных конструкторах копирования.

на практике компиляторы должны синтезировать конструкторы копирования для этих классов, которые демонстрируют поведение, как если бы они выполняли инициализацию memberwise. Но если класс демонстрирует "побитовую семантику копирования" (Lippman, p. 43) тогда компилятору не нужно синтезировать конструктор копирования (что приведет к вызову функции, возможно, встроенному) и делать побитовое копирование. Это утверждение, по-видимому, подкреплено в ARM, но я еще не смотрел это.

использование компилятора для проверки того, что что-то соответствует стандарту, всегда плохая идея, но компиляция ваш код и просмотр результирующей сборки, похоже, проверяют, что компилятор не выполняет инициализацию memberwise в синтезированном конструкторе копирования, а выполняет memcpy вместо:

#include <cstdlib>

class MyClass
{
public:
    MyClass(){};
  int a,b,c;
  double x,y,z;
};

int main()
{
    MyClass c;
    MyClass d = c;

    return 0;
}

сборка, созданная для MyClass d = c; - это:

000000013F441048  lea         rdi,[d] 
000000013F44104D  lea         rsi,[c] 
000000013F441052  mov         ecx,28h 
000000013F441057  rep movs    byte ptr [rdi],byte ptr [rsi] 

...где 28h - это sizeof(MyClass).

это было скомпилировано под MSVC9 в режиме отладки.

EDIT:

длинный и короткий этого поста является то, что:

1) покуда выполнение побитовой копии покажет те же побочные эффекты, что и memberwise copy, стандарт позволяет тривиальным неявным конструкторам копирования делать memcpy вместо копий memberwise.

2) Некоторые компиляторы действительно делают memcpys вместо синтеза тривиального конструктора копирования, который делает memberwise копии.


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


вы может. Но сначала спросите себя:--3-->

Почему бы просто не использовать конструктор копирования, предоставляемый вашим компилятором, чтобы сделать копию члена?

У вас есть конкретные проблемы с производительностью, для которых вам нужно оптимизировать?

текущая реализация содержит все типы POD: что происходит, когда кто-то меняет его?


[...] но есть ли другие скрытые гадости Я должен быть в курсе?

Да: ваш код делает определенные предположения, которые не предлагаются и не документируются (если вы специально не документируете их). Это кошмар для обслуживания.

кроме того, ваша реализация в основном взлома (если это необходимо, это не плохо), и это может зависеть (не уверен в этом) от того, как ваш текущий компилятор реализует вещи.

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

это не то, что решение является необоснованным, это то, что это (или будет) неожиданно для сопровождающий.

чтобы минимизировать это (неожиданность?) Я бы переместил класс в структуру в пространстве имен на основе текущего имени класса, без каких-либо внутренних функций в структуре вообще. Затем вы ясно даете понять, что смотрите на блок памяти и рассматриваете его как блок памяти.

вместо:

class MyClass
{
public:
    MyClass();
    int a,b,c;
    double x,y,z;
};

#define  PageSize 1000000

MyClass Array1[PageSize],Array2[PageSize];

memcpy(Array1,Array2,PageSize*sizeof(MyClass));

вы должны:

namespace MyClass // obviously not a class, 
                  // name should be changed to something meaningfull
{
    struct Data
    {
        int a,b,c;
        double x,y,z;
    };

    static const size_t PageSize = 1000000; // use static const instead of #define


    void Copy(Data* a1, Data* a2, const size_t count)
    {
        memcpy( a1, a2, count * sizeof(Data) );
    }

    // any other operations that you'd have declared within 
    // MyClass should be put here
}

MyClass::Data Array1[MyClass::PageSize],Array2[MyClass::PageSize];
MyClass::Copy( Array1, Array2, MyClass::PageSize );

вот так:

  • дайте понять, что MyClass:: Data структура POD, а не класс (двоичные они будут одинаковыми или очень близкими - одинаковыми, если я правильно помню), но таким образом это также видно программистам, читающим код.

  • централизовать использование memcpy (если вам нужно изменить на std::copy или что-то еще) через два года вы делаете это в одной точке.

  • держите использование memcpy рядом с реализацией структуры POD.


можно использовать memcpy для копирования массива типов POD. И было бы неплохо добавить статический assert для boost::is_pod - Это правда. Ваш класс теперь не является типом POD.

арифметические типы, типы перечислений, типы указателей и указатель на типы членов являются POD.

резюме-квалифицированный версии типа под себя тип Pod.

массив POD сам по себе POD. В struct или Union, все нестатические члены данных РМО, сам стручок если это так:

  • нет пользователей-заявили строители.
  • не private или protected нестатических членов данных.
  • нет базовых классов.
  • нет виртуальных функций.
  • нет нестатических элементов данных ссылочного типа.
  • нет определяемого пользователем оператора присваивания копий.
  • нет, определенные пользователем деструктор.

Я замечу, что вы признаете, что здесь есть проблема. И вы осознаете потенциальные недостатки.

мой вопрос заключается в обслуживании. Вы уверены, что никто никогда не включит в этот класс поле, которое испортит вашу отличную оптимизацию ? Нет, я инженер, а не пророк.

поэтому вместо того, чтобы пытаться улучшить операции копирования.... почему бы не попытаться вообще избежать этого ?

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

например, знаете ли вы о blist (модуль Python). B + дерево может разрешить доступ к индексу с производствами, очень похожими на векторы (немного медленнее, по общему признанию), например, при минимизации количества элементов для перетасовки при вставке / удалении.

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


вызов memcpy для не-POD классов является неопределенным поведением. Предлагаю последовать совету Кирилла. Использование memcpy может быть быстрее, но если операция копирования не имеет критической производительности в коде, просто используйте побитовое копирование.


когда вы говорите о случае, о котором вы говорите, Я предлагаю вам объявить structвместо с класс ' es. Это делает его намного проще читать (и менее спорным:)), а спецификатор доступа по умолчанию является общедоступным.

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


Это будет работать, потому что (POD-) класс такой же, как структура (не полностью, доступ по умолчанию ...), в C++. И вы можете скопировать структуру POD с помощью memcpy.

определение POD не было ни виртуальных функций, ни конструктора, ни деконструктора, ни виртуального наследования ... так далее.