Puis-je utiliser memcpy en C++ pour copier des classes qui n'ont pas de pointeurs ou de fonctions virtuelles

Dites que j'ai une classe, quelque chose comme ce qui suit;

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

#define  PageSize 1000000

MyClass Array1[PageSize],Array2[PageSize];

Si ma classe n'a pas de pointeurs ou de méthodes virtuelles, est-il sûr d'utiliser ce qui suit?

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

La raison pour laquelle je demande, c'est que je traite de très grandes collections de données paginées, comme décrit ici, où les performances sont critiques, et memcpy offre des avantages de performance significatifs par rapport à l'affectation itérative. Je soupçonne que cela devrait être correct, car le pointeur' this ' est un paramètre implicite plutôt que n'importe quoi stocké, mais y a-t-il d'autres méchants cachés dont je devrais être conscient?

Modifier:

Selon les commentaires de sharptooths, les données ne comprennent pas de poignées ou d'informations de référence similaires.

Selon le commentaire de Paul R, j'ai profilé le code, et éviter le constructeur de copie est environ 4,5 fois plus rapide dans ce cas. Une partie de la raison ici est que ma classe de tableau templated est un peu plus complexe que l'exemple simpliste donné, et appelle un placement 'new' quand allocation de mémoire pour les types qui n'autorisent pas la copie superficielle. Cela signifie effectivement que le constructeur par défaut est appelé ainsi que le constructeur de copie.

Deuxième édition

Il est peut-être utile de souligner que j'accepte pleinement que l'utilisation de memcpy de cette manière est une mauvaise pratique et devrait être évitée dans les cas généraux. Le cas spécifique dans lequel il est utilisé fait partie d'une classe de tableau templated haute performance, qui inclut un paramètre 'AllowShallowCopying', qui appellera memcpy plutôt qu'un constructeur de copie. Cela a de grandes implications sur les performances pour les opérations telles que la suppression d'un élément près du début d'un tableau et la pagination des données dans et hors du stockage secondaire. La meilleure solution théorique serait de convertir la classe en une structure simple, mais étant donné que cela implique beaucoup de refactoring d'une grande base de code, l'éviter n'est pas quelque chose que je souhaite faire.

24
demandé sur Community 2010-06-11 12:34:11

10 réponses

Permettez-moi de vous donner une réponse empirique: dans notre application en temps réel, nous le faisons tout le temps, et il fonctionne très bien. C'est le cas dans MSVC pour Wintel et PowerPC et GCC Pour Linux et Mac, même pour les classes qui ont des constructeurs.

Je ne peux pas citer le chapitre et le verset de la norme C++ pour cela, juste des preuves expérimentales.

10
répondu Crashworks 2010-06-11 10:34:59

Selon la norme, si aucun constructeur de copie n'est fourni par le programmeur pour une classe, le compilateur synthétisera un constructeur qui présente initialisation par défaut par membre . (12.8.8) cependant, dans 12.8.1, la norme dit aussi,

Un objet de classe peut être copié en deux moyens, par initialisation (12.1, 8.5), y compris pour l'argument de fonction passage (5.2.2) et pour la valeur de la fonction retour (6.6.3), et par affectation (5.17). Conceptuellement, ces deux les opérations sont implémentées par une copie constructeur (12.1) et affectation de copie opérateur (13.5.3).

Le mot opératoire ici est "conceptuellement", ce qui, selon Lippman donne aux concepteurs du compilateur un 'out' pour faire réellement l'initialisation memberwise dans les constructeurs de copie" trivial " (12.8.6) implicitement définis.

En pratique, alors, les compilateurs doivent synthétiser des constructeurs de copie pour ces classes qui présentent un comportement comme s'ils faisaient memberwise initialisation. Mais si la classe présente une "sémantique de copie au niveau du BIT" (Lippman, p. 43), le compilateur n'a pas besoin de synthétiser un constructeur de copie (ce qui entraînerait un appel de fonction, éventuellement en ligne) et de faire une copie au niveau du BIT à la place. Cette revendication est apparemment sauvegardée dans le bras , mais je n'ai pas encore cherché.

Utiliser un compilateur pour valider que quelque chose est conforme à la norme est toujours une mauvaise idée, mais compiler votre code et afficher l'assemblage résultant semble vérifiez que le compilateur ne fait pas d'initialisation memberwise dans un constructeur de copie synthétisé, mais fait un memcpy à la place:

#include <cstdlib>

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

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

    return 0;
}

L'assemblage généré pour MyClass d = c; est:

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

...où 28h est le sizeof(MyClass).

Ceci a été compilé sous MSVC9 en mode débogage.

Modifier:

Le long et le court de ce post est que:

1) tant que faire une copie binaire présentera les mêmes effets secondaires que la copie memberwise, la norme permet constructeurs de copie implicites triviaux pour faire un memcpy au lieu de copies memberwise.

2) certains compilateurs font en fait memcpy s au lieu de synthétiser un constructeur de copie trivial qui fait des copies memberwise.

11
répondu John Dibling 2010-06-11 11:28:17

Votre classe a un constructeur, et n'est donc pas POD dans le sens où une structure C est. Il n'est donc pas sûr de le copier avec memcpy(). Si vous voulez des données de POD, supprimez le constructeur. Si vous voulez des données non-POD, où la construction contrôlée est essentielle, n'utilisez pas memcpy() - vous ne pouvez pas avoir les deux.

9
répondu 2010-06-11 08:57:51

Vous peut. Mais demandez-vous d'abord:

Pourquoi ne pas simplement utiliser le copy-constructeur fourni par votre compilateur pour faire une copie au niveau des membres?

Avez-vous des problèmes de performances spécifiques pour lesquels vous devez optimiser?

L'implémentation actuelle contient tous les types de POD: que se passe-t-il quand quelqu'un le change?

9
répondu Johnsyweb 2010-06-11 09:15:01

[...] mais quel cachés d'autres méchants Je devrais être au courant?

Oui: votre code fait certaines hypothèses qui ne sont ni suggérées ni documentées (sauf si vous les documentez spécifiquement). C'est un cauchemar pour l'entretien.

En outre, votre implémentation est essentiellement un piratage (si c'est nécessaire, ce n'est pas une mauvaise chose) et cela peut dépendre (pas sûr de cela) de la façon dont votre compilateur actuel implémente les choses.

Cela signifie que si vous mettez à niveau compilateur / chaîne d'outils dans un an (ou cinq) à partir de Maintenant (ou simplement changer les paramètres d'optimisation dans votre compilateur actuel) personne ne se souviendra de ce hack (à moins que vous ne fassiez un gros effort pour le garder visible) et vous risquez de vous retrouver avec un comportement indéfini sur vos mains, et les développeurs maudissant "celui qui a

Ce n'est pas que la décision soit mal fondée, c'est qu'elle est (ou sera) inattendue pour les mainteneurs.

Pour minimiser cela (inattendu?) Je voudrais déplacer la classe dans une structure dans un espace de noms basé sur le nom actuel de la classe, sans aucune fonction interne dans la structure. Ensuite, vous indiquez clairement que vous regardez un bloc de mémoire et le traitez comme un bloc de mémoire.

Au Lieu de:

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));

Vous devriez avoir:

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 );

De Cette façon, vous:

  • Précisez que MyClass:: Data est une structure de POD, pas une classe (binaire, ils seront les mêmes ou très proches - la même chose si je me souviens bien) mais de cette façon il est également visible pour les programmeurs lisant le code.

  • Centralisez l'utilisation de memcpy (si vous devez passer à un std::copy ou autre chose) en deux ans, vous le faites en un seul point.

  • Gardez l'utilisation de memcpy près de l'implémentation de la structure POD.

7
répondu utnapistim 2013-11-07 15:30:12

Vous pouvez utiliser memcpy pour copier un tableau de types de POD. Et ce sera une bonne idée d'ajouter static assert pour boost::is_pod est vrai. Votre classe n'est pas un type de POD maintenant.

Les types arithmétiques, les types d'énumération, les types de pointeurs et les types de pointeurs vers les membres sont des POD.

Une version qualifiée cv d'un type de POD est elle-même un type de POD.

Un tableau de POD est lui-même POD. Une structure ou une union, dont tous les membres de données non statiques sont POD, est elle-même POD si elle a:

  • aucun constructeur déclaré par l'utilisateur.
  • aucun membre de données non statiques privé ou protégé.
  • Pas de classes de base.
  • Pas de fonctions virtuelles.
  • Pas de membres de données non statiques de type de référence.
  • aucun opérateur d'affectation de copie défini par l'utilisateur.
  • aucun destructeur défini par l'utilisateur.
5
répondu Kirill V. Lyadvinsky 2010-06-11 10:47:41

Je remarquerai que vous admettez qu'il y a un problème ici. Et vous êtes conscient des inconvénients potentiels.

Ma question est celle de l'entretien. Vous sentez-vous confiant que personne n'inclura jamais un champ dans cette classe qui gâcherait votre grande optimisation ? Non, Je suis un ingénieur, pas un prophète.

Donc, au lieu d'essayer d'améliorer l'opération de copie.... pourquoi ne pas essayer de l'éviter complètement ?

Serait-il possible de changer la structure de données utilisée pour le stockage de arrêtez de déplacer des éléments... ou, au moins, pas tant que ça.

Par exemple, connaissez-Vous blist (le module Python). B + Tree peut permettre l'accès à l'index avec des performances assez similaires aux vecteurs (Un peu plus lent, certes) par exemple, tout en minimisant le nombre d'éléments à mélanger lors de l'insertion / suppression.

Au lieu d'aller dans le rapide et sale, peut-être devriez-vous vous concentrer sur la recherche d'une meilleure collection ?

3
répondu Matthieu M. 2010-06-11 13:42:34

Appeler memcpy sur des classes non-POD est un comportement indéfini. Je suggère de suivre le Conseil de Kirill pour l'affirmation. L'utilisation de memcpy peut être plus rapide, mais si l'opération de copie n'est pas critique en termes de performances dans votre code, utilisez simplement la copie au niveau du BIT.

1
répondu zoli2k 2010-06-11 08:52:47

En parlant du cas auquel vous faites référence, je vous suggère de déclarer struct's au lieu de class'es. Elle rend beaucoup plus facile à lire (et moins discutable :) ) et le spécificateur d'accès par défaut est public.

Bien sûr, vous pouvez utiliser memcpy dans ce cas, mais méfiez - vous que l'ajout d'autres types d'éléments dans la structure (comme les classes c++) n'est pas recommandé (pour des raisons évidentes-vous ne savez pas comment memcpy les influencera).

1
répondu INS 2010-06-11 09:17:29

Cela fonctionnera, car une classe (POD-) est la même qu'une structure (pas complètement, accès par défaut ...), en C++. Et vous pouvez copier une structure POD avec memcpy.

La définition de POD était pas de fonctions virtuelles, pas de constructeur, déconstructeur pas d'héritage virtuel ... etc.

0
répondu Christopher 2010-06-11 08:42:28