Je comprends que l'attribution de tableaux par membre n'est pas prise en charge, de sorte que ce qui suit ne fonctionnera pas:
int num1[3] = {1,2,3};
int num2[3];
num2 = num1; // "error: invalid array assignment"
Je viens d'accepter cela comme un fait, en pensant que le but du langage est de fournir un cadre ouvert et de laisser l'utilisateur décider comment implémenter quelque chose comme la copie d'un tableau.
Cependant, ce qui suit fonctionne:
struct myStruct { int num[3]; };
struct myStruct struct1 = {{1,2,3}};
struct myStruct struct2;
struct2 = struct1;
Le tableau num[3]
est affecté par membre à partir de son instance dans struct1
, dans son instance dans struct2
.
Pourquoi l'affectation des tableaux par membre est-elle prise en charge pour les structures, mais pas en général?
edit : Commentaire de Roger Pate dans le thread std :: string dans la structure - Problèmes de copie / affectation? semble indiquer la direction générale de la réponse, mais je n'en sais pas assez pour la confirmer moi-même.
edit 2 : Beaucoup d'excellentes réponses. J'ai choisi Luther Blissett parce que je m'interrogeais surtout sur la logique philosophique ou historique derrière le comportement, mais la référence de James McNellis à la documentation de spécification associée était également utile.
memcpy()
ou similaire.boost::array
( boost.org/doc/libs/release/doc/html/array.html ) et maintenantstd::array
( en.cppreference.com/w/cpp/container/array ) sont des alternatives compatibles STL au vieux tableaux C en désordre. Ils prennent en charge l'affectation de copie.Réponses:
Voici mon point de vue:
Le développement du langage C offre un aperçu de l'évolution du type de tableau en C:
Je vais essayer de décrire le tableau:
Les précurseurs B et BCPL de C n'avaient pas de type de tableau distinct, une déclaration comme:
auto V[10] (B) or let V = vec 10 (BCPL)
déclarerait V comme étant un pointeur (non typé) qui est initialisé pour pointer vers une région inutilisée de 10 "mots" de mémoire. B déjà utilisé
*
pour pointer déréférencement et a la[]
notation de sténographie,*(V+i)
signifiaitV[i]
, comme dans aujourd'hui C / C. Cependant, ceV
n'est pas un tableau, c'est toujours un pointeur qui doit pointer vers une certaine mémoire. Cela a causé des problèmes lorsque Dennis Ritchie a essayé d'étendre B avec des types de structure. Il voulait que les tableaux fassent partie des structures, comme en C aujourd'hui:struct { int inumber; char name[14]; };
Mais avec le concept B, BCPL des tableaux en tant que pointeurs, cela aurait exigé que le
name
champ contienne un pointeur qui devait être initialisé à l'exécution sur une région mémoire de 14 octets dans la structure. Le problème d'initialisation / mise en page a finalement été résolu en donnant aux tableaux un traitement spécial: le compilateur suivrait l'emplacement des tableaux dans les structures, sur la pile, etc. sans nécessiter réellement la matérialisation du pointeur vers les données, sauf dans les expressions qui impliquent les tableaux. Ce traitement permettait à presque tout le code B de continuer à s'exécuter et est la source de la règle «les tableaux convertis en pointeur si vous les regardez» . C'est un hack de compatibilité, qui s'est avéré très pratique, car il permettait des tableaux de taille ouverte, etc.Et voici ma supposition pourquoi le tableau ne peut pas être attribué: Puisque les tableaux étaient des pointeurs en B, vous pouvez simplement écrire:
auto V[10]; V=V+5;
pour rebaser un "tableau". Cela n'avait plus de sens, car la base d'une variable de tableau n'était plus une lvalue. Donc, cette attribution a été interdite, ce qui a aidé à attraper les quelques programmes qui ont fait ce rebasage sur des tableaux déclarés. Et puis cette notion est restée: comme les tableaux n'ont jamais été conçus pour être des citations de première classe du système de type C, ils étaient principalement traités comme des bêtes spéciales qui deviennent des pointeurs si vous les utilisez. Et d'un certain point de vue (qui ignore que les C-arrays sont un hack bâclé), interdire l'affectation de tableaux a encore du sens: un tableau ouvert ou un paramètre de fonction de tableau est traité comme un pointeur sans informations de taille. Le compilateur ne dispose pas des informations pour générer une affectation de tableau pour eux et l'affectation de pointeur était requise pour des raisons de compatibilité.
/* Example how array assignment void make things even weirder in C/C++, if we don't want to break existing code. It's actually better to leave things as they are... */ typedef int vec[3]; void f(vec a, vec b) { vec x,y; a=b; // pointer assignment x=y; // NEW! element-wise assignment a=x; // pointer assignment x=a; // NEW! element-wise assignment }
Cela n'a pas changé lorsqu'une révision de C en 1978 a ajouté l'affectation de structure ( http://cm.bell-labs.com/cm/cs/who/dmr/cchanges.pdf ). Même si les enregistrements étaient des types distincts en C, il n'était pas possible de les affecter au début de K&R C. Vous deviez les copier membre par membre avec memcpy et vous ne pouviez leur passer que des pointeurs comme paramètres de fonction. L'assignation (et le passage des paramètres) était maintenant simplement définie comme le memcpy de la mémoire brute de la structure et comme cela ne pouvait pas casser le code existant, il était facilement adopté. En tant qu'effet secondaire involontaire, cela introduisit implicitement une sorte d'assignation de tableau, mais cela se produisait quelque part dans une structure, donc cela ne pouvait pas vraiment poser de problèmes avec la façon dont les tableaux étaient utilisés.
la source
int[10] c;
pour que la lvaluec
se comporte comme un tableau de dix éléments, plutôt que comme un pointeur vers le premier élément d'un tableau de dix éléments. Il y a quelques situations où il est utile de pouvoir créer un typedef qui alloue de l'espace lorsqu'il est utilisé pour une variable, mais passe un pointeur lorsqu'il est utilisé comme argument de fonction, mais l'incapacité d'avoir une valeur de type tableau est une faiblesse sémantique importante dans la langue.Concernant les opérateurs d'affectation, le standard C ++ dit ce qui suit (C ++ 03 §5.17 / 1):
Un tableau n'est pas une lvalue modifiable.
Cependant, l'affectation à un objet de type classe est définie spécialement (§5.17 / 4):
Donc, nous regardons pour voir ce que fait l'opérateur d'affectation de copie implicitement déclaré pour une classe (§12.8 / 13):
Ainsi, pour un objet de type classe, les tableaux sont copiés correctement. Notez que si vous fournissez un opérateur d'affectation de copie déclaré par l'utilisateur, vous ne pouvez pas en profiter et vous devrez copier le tableau élément par élément.
Le raisonnement est similaire en C (C99 §6.5.16 / 2):
Et §6.3.2.1 / 1:
En C, l'affectation est beaucoup plus simple qu'en C ++ (§6.5.16.1 / 2):
Pour l'affectation d'objets de type struct, les opérandes gauche et droit doivent avoir le même type, de sorte que la valeur de l'opérande droit est simplement copiée dans l'opérande gauche.
la source
=
nécessite une rvalue sur le RHS et un tableau ne peut pas être une rvalue ! La conversion lvalue-to-rvalue est interdite pour les tableaux, remplacée par lvalue-to-pointer.static_cast
n'est pas meilleur pour créer une rvalue car elle est définie dans les mêmes termes.Dans ce lien: http://www2.research.att.com/~bs/bs_faq2.html, il y a une section sur l'affectation des tableaux:
Les deux problèmes fondamentaux avec les tableaux sont que
Et je pense que c'est la différence fondamentale entre les tableaux et les structures. Une variable de tableau est un élément de données de bas niveau avec une connaissance de soi limitée. Fondamentalement, c'est un morceau de mémoire et un moyen de s'y indexer.
Ainsi, le compilateur ne peut pas faire la différence entre int a [10] et int b [20].
Les structures, cependant, n'ont pas la même ambiguïté.
la source
sizeof(a)
rapportsizeof(b)
ou passera
àvoid f(int (&)[20]);
.Je sais, tous ceux qui ont répondu sont des experts en C / C ++. Mais j'ai pensé que c'était la raison principale.
num2 = num1;
Ici, vous essayez de changer l'adresse de base du tableau, ce qui n'est pas autorisé.
et bien sûr, struct2 = struct1;
Ici, l'objet struct1 est affecté à un autre objet.
la source
num2 = num1
ils se comporteraient parfaitement. Les éléments denum2
auraient la même valeur que l'élément correspondant denum1
.Une autre raison pour laquelle aucun effort supplémentaire n'a été fait pour renforcer les tableaux en C est probablement que l'affectation de tableaux ne serait pas si utile. Même si cela peut être facilement réalisé en C en l'enveloppant dans une structure (et l'adresse de la structure peut être simplement convertie à l'adresse du tableau ou même à l'adresse du premier élément du tableau pour un traitement ultérieur), cette fonctionnalité est rarement utilisée. L'une des raisons est que les tableaux de tailles différentes sont incompatibles, ce qui limite les avantages de l'affectation ou, en relation, du passage aux fonctions par valeur.
La plupart des fonctions avec des paramètres de tableau dans les langages où les tableaux sont des types de première classe sont écrites pour des tableaux de taille arbitraire. La fonction effectue ensuite une itération sur le nombre d'éléments donné, une information fournie par le tableau. (En C, l'idiome est, bien sûr, de passer un pointeur et un nombre d'éléments séparés.) Une fonction qui accepte un tableau d'une seule taille spécifique n'est pas nécessaire aussi souvent, donc il ne manque pas grand chose. (Cela change lorsque vous pouvez laisser au compilateur le soin de générer une fonction distincte pour toute taille de tableau, comme avec les modèles C ++; c'est la raison pour laquelle
std::array
est utile.)la source