web-dev-qa-db-fra.com

Le bit std :: array est-il compatible avec l'ancien tableau C?

La représentation sous-jacente des bits pour un std::array<T,N> v Et un T u[N] Est-elle la même?

En d'autres termes, est-il sûr de copier N*sizeof(T) octets de l'un à l'autre? (Soit par reinterpret_cast Ou memcpy.)

Modifier:

Par souci de clarté, l'accent est mis sur la même représentation binaire et reinterpret_cast.

Par exemple, supposons que j'ai ces deux classes sur certains types trivialement copiables T, pour certains N:

struct VecNew {
    std::array<T,N> v;
};

struct VecOld {
    T v[N];
};

Et il y a la fonction héritée

T foo(const VecOld& x);

Si les représentations sont les mêmes, cet appel est sûr et évite de copier:

VecNew x;
foo(reinterpret_cast<const VecOld&>(x));
33
shinjin

Je dis oui (mais la norme ne le garantit pas).

Selon [tableau]/2:

Un tableau est un agrégat ([ dcl.init.aggr ]) qui peut être initialisé par liste avec jusqu'à N éléments dont les types sont convertibles en T.

Et [dcl.init.aggr]:

Un agrégat est un tableau ou une classe (Clause [classe]) avec

  • aucun constructeur fourni par l'utilisateur, explicite ou hérité ([class.ctor]),

  • aucun membre de données non statique privé ou protégé (Clause [class.access]),

  • aucune fonction virtuelle ([class.virtual]), et

  • aucune classe de base virtuelle, privée ou protégée ([class.mi]).

À la lumière de cela, "peut être initialisé par liste" n'est possible que s'il n'y a pas d'autres membres au début de la classe et pas de vtable.

Ensuite, data() est spécifié comme:

constexpr T* data() noexcept;

Renvoie: Un pointeur tel que [data(), data() + size()) est une plage valide et data() == addressof(front()).

La norme veut essentiellement dire "il renvoie un tableau" mais laisse la porte ouverte à d'autres implémentations.

La seule autre implémentation possible est une structure avec des éléments individuels, auquel cas vous pouvez rencontrez des problèmes d'alias. Mais à mon avis, cette approche n'ajoute rien d'autre que de la complexité. Il n'y a rien à gagner en déroulant un tableau dans une structure.

Cela n'a donc aucun sens pas pour mettre en œuvre std::array en tant que tableau.

Mais une faille existe.

9
rustyx

Cela ne répond pas directement à votre question, mais vous devez simplement utiliser std::copy :

T c[N];
std::array<T, N> cpp;

// from C to C++
std::copy(std::begin(c), std::end(c), std::begin(cpp));

// from C++ to C
std::copy(std::begin(cpp), std::end(cpp), std::begin(c));

Si T est un type trivialement copiable, cela se compilera jusqu'à memcpy. Si ce n'est pas le cas, cela fera une affectation de copie par élément et sera correct. Quoi qu'il en soit, cela fait la bonne chose et est tout à fait lisible. Aucune arithmétique d'octets manuels n'est nécessaire.

20
Barry

std::array fournit la méthode data () qui peut être utilisée pour copier vers/depuis un tableau de style c de taille appropriée:

const size_t size = 123;
int carray[size];
std::array<int,size> array;

memcpy( carray, array.data(), sizeof(int) * size );
memcpy( array.data(), carray, sizeof(int) * size );

Comme indiqué sur documentation

Ce conteneur est un type agrégé avec la même sémantique qu'une structure contenant un tableau de style C T [N] comme seul membre de données non statique.

il semble donc que l'empreinte mémoire soit compatible avec le tableau de style c, bien qu'il ne soit pas clair pourquoi vous voulez utiliser des "hacks" avec reinterpret_cast lorsqu'il existe un moyen approprié qui n'a pas de surcharge.

13
Slava

La condition sur la méthode data() est qu'elle retourne un pointeur T* Tel que:

[data(), data() + size()) est une plage valide et data() == addressof(front()).

Cela implique que vous pouvez accéder à chaque élément séquentiellement via le pointeur data(), et donc si T est copiable, vous pouvez en effet utiliser memcpy pour copier sizeof(T) * size() octets vers/depuis un tableau T[size()], car cela équivaut à memcpying chaque élément individuellement.

Cependant, vous ne pouvez pas utiliser reinterpret_cast, Car cela violerait un alias strict, car data() n'est pas obligé de en fait être soutenu par un tableau - et aussi, même si vous deviez garantir que std::array contient un tableau, puisque C++ 17 vous ne pouvez pas (même en utilisant reinterpret_cast) convertir un pointeur vers un tableau vers/depuis un pointeur vers son premier membre (vous avez pour utiliser std::launder).

9
ecatmur

array n'impose pas grand-chose sur le type sous-jacent sur lequel vous l'instanciez.

Pour avoir toute possibilité de résultats utiles en utilisant memcpy ou reinterpret_cast pour faire une copie, le type sur lequel vous l'avez instancié devrait être trivialement copiable. Le stockage de ces éléments dans un array n'affecte pas l'exigence que memcpy (et autres) ne fonctionne qu'avec des types trivialement copiables.

array doit être un conteneur contigu et un agrégat, ce qui signifie à peu près que le stockage des éléments doit être un tableau. La norme le montre comme:

T elems[N]; // exposition only

Plus tard, cependant, il a une note qui implique au moins que ce soit un tableau est requis (§ [array.overview]/4):

[Remarque: La variable membre elems est présentée à titre d'exemple uniquement, pour souligner que array est un agrégat de classe. Le nom elems ne fait pas partie de l'interface du tableau. —fin note]

[non souligné dans l'original]

Notez que ce n'est vraiment que le nom spécifique elems qui n'est pas requis.

7
Jerry Coffin