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

Вы можете использовать numpy.random.choice

import numpy as np

x = [10, 20, 30, 40, 50]

print(np.random.choice(x, 4, replace=True))

Вывод:

[50 50 30 30] 

31
задан Community 23 May 2017 в 12:10
поделиться

11 ответов

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

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

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

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

Использование компилятора для проверки соответствия чего-либо стандарту - всегда плохая идея, но компиляция вашего кода и просмотр полученной сборки, похоже, подтверждает, что компилятор не выполняет инициализацию членов в синтезированном конструкторе копирования, а делает 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 в режиме Debug.

EDIT:

Суть этого сообщения такова:

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

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

14
ответ дан 27 November 2019 в 22:07
поделиться

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

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

12
ответ дан Crashworks 27 November 2019 в 22:07
поделиться

Вы могли . Но сначала спросите себя:

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

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

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

9
ответ дан Johnsyweb 27 November 2019 в 22:07
поделиться

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

1
ответ дан zoli2k 27 November 2019 в 22:07
поделиться

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

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

0
ответ дан Christopher 27 November 2019 в 22:07
поделиться

Как указано John Dibling, Вы не должны использовать memcpy вручную. Вместо этого используйте std::copy. Если Ваш класс будет memcpy-быть-в-состоянии, [то 113] автоматически сделает memcpy. Это может быть еще быстрее, чем ручной memcpy.

, Если Вы используете std::copy, Ваш код читаем, и он всегда использует самый быстрый способ скопировать. И если Вы измените расположение своего класса позже так, чтобы это больше не memcpy-было-в-состоянии, код, который использует std::copy, не повредится, в то время как Ваши вызовы руководства к memcpy будут.

Теперь, как Вы знаете, memcpy-ли Ваш класс-в-состоянии? Таким же образом, std::copy обнаруживает это. Это использует: std::is_trivially_copyable . Можно использовать static_assert, чтобы удостовериться, что это свойство поддерживается.

Примечание, что std::is_trivially_copyable может только проверить информацию о типе. Это не понимает семантику. Следующий класс является тривиально типом copyable , но поразрядная копия была бы ошибкой:

#include <type_traits>

struct A {
  int* p = new int[32];
};

static_assert(std::is_trivially_copyable<A>::value, "");

После того, как поразрядная копия, ptr из копии все еще укажет на исходную память. Также посмотрите Правило Три .

0
ответ дан 27 November 2019 в 22:07
поделиться

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

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

Cv-квалифицированная версия типа POD сама является типом POD.

Массив POD сам является POD. Структура или объединение, все нестатические члены данных которого являются POD, сами являются POD, если у них:

  • Нет объявленных пользователем конструкторов.
  • Нет приватных или защищенных нестатических членов данных.
  • Нет базовых классов.
  • Никаких виртуальных функций.
  • Никаких нестатических членов данных ссылочного типа.
  • Нет определяемого пользователем оператора присвоения копий.
  • Нет определяемого пользователем деструктора.
5
ответ дан 27 November 2019 в 22:07
поделиться

[...] но есть ли еще какие-нибудь скрытые неприятности. о которых я должен знать?

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

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

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

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

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

Вместо:

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.

8
ответ дан 27 November 2019 в 22:07
поделиться

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

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

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

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

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

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

3
ответ дан 27 November 2019 в 22:07
поделиться

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

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

1
ответ дан 27 November 2019 в 22:07
поделиться

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

9
ответ дан 27 November 2019 в 22:07
поделиться
Другие вопросы по тегам:

Похожие вопросы: