Le calendrier de l’Avent du C++ continue. Après quelques trous dans le calendrier, aujourd’hui une nouvelle surprise : le court-circuit du constructeur de copie.
Cette fonctionnalité est présente dans le C++ depuis la nuit des temps et pourtant peu connue, alors que ses effets de bords peuvent être redoutables. Cette dépêche très pédagogique explique tous les détails d’une optimisation ultime.
Sommaire
- Terminologie
- Le constructeur de copie peut être coûteux
- Élider le constructeur de copie
- Explications
- Et le constructeur de déplacement ?
- Cas autorisés
- Problématiques
- Conclusion
- Réutilisation
- Les auteurs
- Continuer à améliorer ce document
- Appel à contribution
Terminologie
Cette dépêche contient des mots français peu utilisées au quotidien. Ces mots sont le résultat de la traduction des termes du standard C++. Pour ne pas être trop perdu, un petit tour de la signification spécifique à cette dépêche.
Élision
- « élision du constructeur de copie » : traduction de l’anglais « copy elision » qui est une optimisation évitant de passer par le constructeur de copie ;
- « élider » : en anglais « elide », supprimer le constructeur de copie ;
- « éluder » : éviter avec adresse le constructeur de copie, éclipser, court‐circuiter, outrepasser, contourner.
Cette dépêche n’utilise pas « éluder », mais « élider », car l’orthographe est proche du terme anglais « elide ».
Constructeur de copie
Cette dépêche utilise la traduction « constructeur de copie » pour l’expression anglaise « copy constructor ». D’autres documents en français utilisent des traductions sensiblement différentes :
- « constructeur par copie » ;
- « constructeur de recopie » ;
- « constructeur par recopie ».
Les auteurs de cette dépêche ont opté pour l’expression « constructeur de copie », car sémantiquement le mot « de » identifie, alors que le mot « par » décrit. Et car l’orthographe de « copie » est plus proche de l’anglais « copy » (un peu comme choisir « paquet » au lieu de « paquetage » pour traduire « package » en informatique). Il en est de même pour « constructeur de déplacement ».
Si vous souhaitez apporter vos idées sur la traduction, merci de vos commentaires constructifs (par copie). ;-)
Le constructeur de copie peut être coûteux
Lors des appels et retours de fonctions, il arrive qu’un objet temporaire soit créé, avant d’être copié à son emplacement mémoire définitif. Cette approche consomme inutilement des ressources (mémoire, temps d’exécution).
struct GrosseStructure
{
// ... des attributs volumineux
// et des références vers d'autres objets
};
GrosseStructure f()
{
GrosseStructure immense;
// ...
// Copie de l'objet immense
// par retour de fonction
return immense;
}
void g (GrosseStructure arg)
{
// ...
}
int main()
{
// Copie d'un objet temporaire
// dans l'appel de fonction g()
g( f() );
}
Élider le constructeur de copie
Dans certains cas, il est possible d’éviter la copie, en créant directement l’objet à son emplacement de destination final. Le C++98 autorise le compilateur à faire une telle optimisation.
L’exemple suivant construit un objet a
par une succession de six constructeurs de copie.
GCC et Clang n’exécutent pas le constructeur de copie. Ces deux compilateurs optimisent le code, même avec l’option -O0
(qui signifie pourtant « pas d’optimisation »). Cette optimisation est quand même désactivée avec -fno-elide-constructors
.
#include <iostream>
struct A
{
int i; // incrémenté à chaque copie
A() { i = 0; }
A(A const& a) { i = a.i + 1; }
};
A f() { return A(A()); }
int main()
{
A a = A( A( A( f() ) ) );
std::cout << "a.i = " << a.i << std::endl;
}
Option de compilation | GCC-6.2 | Clang-3.8 | ||
---|---|---|---|---|
-std=c++98 -O0 |
a.i = 0 |
a.i = 0 |
||
-fno-elide-constructors |
a.i = 6 |
a.i = 6 |
Explications
Dans l’exemple précédent, la fonction f()
crée un objet temporaire A
. Le compilateur élide le constructeur de copie et crée cet objet A
directement dans la pile d’appel (call stack) de la fonction appelante main()
.
Le compilateur court‐circuite autant de fois que nécessaire. Cette optimisation s’applique aussi à l’initialisation de copie (copy initialization). Ainsi, l’objet temporaire retourné par la fonction f()
est directement construit dans a
. Et cela indépendamment de l’extension inline. C’est‐à‐dire que le corps de la fonction f()
aurait pu se trouver dans une bibliothèque à part.
Plus généralement, et contrairement au langage C, le standard C++ autorise le compilateur à effectuer toutes les optimisations possibles du moment que le résultat soit conforme à celui attendu par le code source. Et l’élision du constructeur de copie va plus loin, car le standard C++ part du principe que les développeurs codent le constructeur de copie pour faire la même chose que les autres constructeurs, le résultat des constructeurs se valent. Le compilateur ne vérifie pas si les corps des différents constructeurs font la même chose (ce serait trop compliqué).
Avertissement : Le standard C++ privilégie cette optimisation, quitte à ne pas exécuter les instructions du constructeur de copie. Si ton constructeur de copie réalise des opérations particulières parce que tu es certain qu’il sera forcément appelé avec A(A())
, alors le compilateur risque de te faire tourner en bourrique !
Et le constructeur de déplacement ?
Le compilateur remplace le constructeur de copie par le constructeur de déplacement quand cela est possible. En revanche, généralement, le compilateur préfère élider le constructeur de copie quand cela est possible, donc ne pas utiliser le constructeur par déplacement.
Nous pouvons nous poser la même question pour les opérateurs d’affectation de copie et de déplacement. Donc, expérimentons un peu plus en généralisant le précédent exemple :
#include <iostream>
int t[6] = { 0, 0, 0, 0, 0, 0 };
struct A
{
A() { ++t[0]; }
A(A const&) { ++t[1]; }
A& operator=(A const&) { ++t[2]; return *this; }
#if __cplusplus > 201100
A(A &&) { ++t[3]; }
A& operator=(A &&) { ++t[4]; return *this; }
#endif
~A() { ++t[5]; }
};
A f() { return A( A() ); }
int main()
{
{ A a = A( A( A( f() ) ) ); }
std::cout << "Dflt = " << t[0] << "\n"
"Copy = " << t[1] << "\n"
"CpAs = " << t[2] << "\n"
#if __cplusplus > 201100
"Move = " << t[3] << "\n"
"MvAs = " << t[4] << "\n"
#endif
"Dtor = " << t[5] << '\n';
}
Quatre tests produisent le résultat Élision :
g++ -std=c++98 ; clang++ -std=c++98
g++ -std=c++11 ; clang++ -std=c++11
Deux tests produisent le résultat C++98 :
g++ -std=c++98 -fno-elide-constructors
clang++ -std=c++98 -fno-elide-constructors
Deux tests produisent le résultat C++11 :
g++ -std=c++11 -fno-elide-constructors
clang++ -std=c++11 -fno-elide-constructors
Résultats | Élision | C++98 | C++11 |
---|---|---|---|
Dflt Constructeur par défaut |
1 | 1 | 1 |
Copy Constructeur de copie |
- | 6 | - |
CpAs Opérateur d’affectation de copie |
- | - | - |
Move Constructeur de déplacement |
- | - | 6 |
MvAs Opérateur d’affectation de déplacement |
- | - | - |
Dtor Destructeur |
1 | 7 | 7 |
Cas autorisés
Le standard C++11 définit les trois cas suivants pour lesquels le compilateur peut élider le constructeur de copie.
Cas 1 : Un objet temporaire utilisé pour initialiser un autre objet
Ci‐dessous, l’objet temporaire A()
peut bénéficier de l’élision. Dans ce cas, le constructeur par défaut A()
crée l’argument a
directement sur la pile d’appel de la fonction f()
:
void f (A a) {}
int main()
{
f( A() );
} // ^--- objet temporaire
Donc, l’objet temporaire n’est pas copié, mais directement créé à l’emplacement mémoire de destination, comme si le constructeur de copie avait été appelé.
Cas 2 : Retour par valeur pour une variable sur le point de sortir de sa portée
Ce sont les fameux « Return Value Optimization » (RVO) et « Named Return Value Optimization » (NRVO) que nous pourrions traduire par :
-
Optimisation du retour par valeur :
A rvo() { return A(); } int main() { A a = rvo(); }
-
Optimisation du retour par valeur à partir d’une variable nommée :
A nrvo() { A a; return a; } int main() { A a = nrvo(); }
Cas 3 : Levée ou capture d’une exception par valeur
Note : Dans ce cas, GCC ne réalise pas l’élision. Ce n’est pas parce que le standard l’autorise que les compilateurs doivent l’implémenter. Et ce n’est pas parce que le standard C++98 a normalisé cette pratique que les compilateurs le faisaient depuis les années 90. Les compilateurs les plus populaires ont progressivement implémenté ces optimisations au fur des années.
void f()
{
A a;
throw a;
}
int main()
{
try {
f();
} catch (A a) {
//…
}
}
Problématiques
Ces optimisations laissées à la discrétion des compilateurs posent quelques problèmes ennuyeux. Le TS P0135r0 indique que le fil de discussion sur ISO C++ Future Proposals a produit une longue liste des inconvénients. Ces inconvénients découlent de la formulation actuelle du standard C++ à propos de cette élision. Les améliorations effectuées entre C++98 et C++14 n’ont pas supprimé ces inconvénients. Voici trois inconvénients parmi les plus notables :
Inconvénient 1 : Type indéplaçable
Le code qui exploite l’élision se heurte aux types indéplaçables. Dans l’exemple suivant, la fonction f()
retourne un objet par valeur. Le compilateur sait qu’il peut élider le constructeur de copie et que le constructeur de déplacement ne lui est pas utile. Le développeur sait que le compilateur peut le faire. Tous, humains et logiciels sont d’accord pour dire que ce code est possible.
Mais pas le standard !
struct NonDeplacable
{
NonDeplacable() = default;
NonDeplacable(NonDeplacable const&) = default;
NonDeplacable(NonDeplacable &&) = delete;
}; // ^-- pas de constructeur de déplacement
NonDeplacable f()
{
return NonDeplacable();
// Clang-3.9 error: call to deleted constructor of 'NonDeplacable'
// GCC-7 error: use of deleted function 'NonDeplacable::NonDeplacable(NonDeplacable&&)'
}
int main()
{
NonDeplacable x = f();
}
Pour rappel, cette optimisation est optionnelle. Et donc, le standard C++ en interdisant l’élision dans ce cas, évite du code C++ « valide » qui ne puisse pas être compilé par les compilateurs ne sachant pas élider.
Cette situation rend très difficile (voire impossible) l’implémentation d’une fabrique ou d’un constructeur nommé pour les types non déplaçables.
De plus, le standard ne permet pas non plus l’initialisation par inférence des objets non déplaçables, c’est‐à‐dire avec le mot‐clef auto
. Alors que Herb Sutter, animateur en chef du comité de standardisation du C++, préconise de recourir, le plus souvent, au mot‐clef auto
.
int main()
{
auto x = f(); // Erreur
}
Inconvénient 2 : Un code portable est moins performant que l’élision
L’écriture d’un code portable ne peut pas se baser sur une hypothétique optimisation, d’autant plus si la construction de copie présente des effets de bords, chose peu recommandable de fait. Par exemple, un développeur pourrait hésiter entre ces deux approches ci‐dessous.
2.1. Retour par valeur
Si le compilateur n’élide pas, l’appel du constructeur de copie peut devenir pénalisant, donc ce n’est pas portable (pour des questions de performance).
A f1()
{
A a;
a.data = 42;
return a;
}
int main()
{
A a = f1();
}
Heureusement, le C++11 prend le relai et impose que la valeur retournée soit non pas copiée, mais déplacée. Si l’objet occupe plus de mémoire que ce que son sizeof
va retourner (cas des chaînes, des vecteurs, etc.), alors déplacer va faire une différence : on gagne une copie et ce n’est pas négligeable. En revanche, si l’objet est simple et que son sizeof
compte vraiment toute l’information qu’il représente (comme un quaternion), déplacer va coûter aussi cher que copier.
2.2. Paramètre de sortie
Le compilateur écrit directement dans la donnée préalablement construite par l’appelant. Cela permet de limiter la casse si le compilateur n’élide pas, mais cela peut être moins performant que l’élision.
void f2 (A& a)
{
a.data = 42; // on écrit directement dedans
// ou on déplace par affectation b dans a
A b;
a = std::move(b);
}
int main()
{
A a;
f2(a);
}
Cette technique présente à la fois des avantages et des inconvénients. Elle est très dépendante du contexte.
D’un côté, le compilateur ne saura plus résoudre correctement les problèmes d’aliasing sur des fonctions non inline.
De l’autre, on évite de construire un A
à chaque appel. Si A
est un gros objet, un vector<>
par exemple, même avec l’élision, à chaque appel on construit et on libère un objet A
. L’approche du paramètre sortant f2(A&a)
peut à la place redimensionner ce vector<>
et le remplir ensuite. Un code appelant f2(A&a)
en boucle permet d’économiser des millions d’allocations et de libérations, l’objet A
étant alors un cache extérieur. Les gains réalisés ainsi sont intéressants.
Inconvénient 3 : Un code portable est moins naturel
C’est le point le plus important de cette dépêche, l’intérêt d’écrire A f1()
plutôt que void f1(A& out)
. Mais pourquoi donc cette dépêche s’obstine à vouloir écrire A f1()
alors que void f1(A& out)
fait très bien l’affaire ?
Car utiliser A f1()
est bien plus naturel !
En mathématiques, nous écrivons y = f(x)
, c’est naturel, notre cerveau est habitué à penser comme cela. Alors pourquoi on devrait écrire f(x_in,y_out)
?
Réponse : Les développeurs sont priés d’écrire f(x_in,y_out)
car y = f(x)
n’est pas portable (pour les raisons évoquées sur les points précédents).
Ce serait bien de pouvoir coder avec le retour par valeur A f() { return A(); }
afin de porter la sémantique d’exécution du logiciel et donc de mieux comprendre et maintenir le code source, non ?
Conclusion
Cette dépêche présente une optimisation intéressante permise par le standard C++, mais qui malheureusement n’est pas vraiment exploitée.
Alors, que faire pour résoudre tous ces inconvénients et démocratiser les possibilités de code offertes par l’élision du constructeur de copie ?
Réponse surprise dans la prochaine dépêche… :-)
Réutilisation
Le texte de cette dépêche est protégé par le droit d’auteur la gauche d’auteur et réutilisable sous licence CC BY-SA 4.0. Les images utilisées sont aussi sous licence libre (cliquer sur l’image pour plus de détails).
Donc, n’hésitez pas à réutiliser ce contenu libre pour créer, par exemple, des supports de formation, des présentations (meetups), des publications sur d’autres blogs, des articles pour des magazines, et aussi un article C++17 sur Wikipédia dès que Wikipédia utilisera la licence CC BY-SA 4.0. ٩(•‿•)۶
Les auteurs
Par respect de la licence, merci de créditer les nombreux auteurs :
- les principaux auteurs sont Adrien Jeser, lmg HS, gbdivers et Oliver H. ;
- les nombreux autres contributeurs ayant contribué sur l’ancêtre de cette dépêche ou sur le dépôt Git sont eggman, Yves Bourguignon, Storm, gorbal, palm123, khivapia, BAud, Segfault, Benoît Sibaud, Lucas, cracky, Martin Peres, RyDroid, olibre et Guss.
Continuer à améliorer ce document
Malgré tout le soin apporté, il reste certainement des oublis, des ambiguïtés, des fôtes… Bien que cette dépêche restera figée sur le site LinuxFr.org, il est possible de continuer à l’enrichir sur le dépôt Git du Groupe des utilisateurs C++ francophone (C++FRUG). C’est donc sur ce dépôt que se trouve les versions les plus à jour. (ღ˘⌣˘ღ)
Appel à contribution
Nous nous efforçons de vous fournir une dépêche de qualité chaque jour ouvré. Et en plus, avec une illustration sympathique. Mais cela demande beaucoup de travail et tenir les délais n’est pas toujours simple.
Merci de nous donner un coup de main, que ce soit sur la correction orthographique, le dessin, la lecture des spécifications techniques, la rédaction d’une nouvelle dépêche à intégrer au calendrier de l’Avent du C++. Tu peux aussi apporter ta contribution à la dépêche Faut‐il continuer à apprendre le C++ ?.
Rejoins‐nous sur la page du groupe de travail C++ sur LinuxFr.org (un compte est nécessaire pour y accéder).
À suivre…
# What you see is not what you get
Posté par AncalagonTotof . Évalué à 10.
Hello,
Tout d'abord, merci pour cette série d'articles, très denses ! Ça remet les idées en place !
Et ça fait peur … Sans prétendre au niveau gourou, je pensais avoir un niveau correct en C++. Ici, je m'aperçois que mes fondations ne sont pas si solides.
Ce qui me dérange le plus, c'est que le code que je lis ne fasse pas ce qu'il semble vouloir dire.
Pire, selon le compilateur et ses options, le comportement change.
Dans un autre registre, je déteste particulièrement les templates. À la moindre connerie, je me retrouve avec des dizaines ou centaines d'erreurs de compilation qui me rappellent le Cobol sur AS400 à l'IUT en 1990 …
Du coup, j'évite d'en écrire. Et je ne les utilise que quand c'est nécessaire (le plus souvent, celles de Qt).
Mon taff ne me laisse pas le loisir d'apprendre par cœur la norme C++. Qui parmi vous en a le temps ? Qui peut prétendre être un gourou C++ ? Très peu de personnes je pense.
Jamais je n'aurai toutes ces subtilités en tête.
À quoi bon mettre tout ça dans la norme si, au mieux, personne ne les utilises, et, au pire, ça génère des prises de tête ou des bugs ?
[^] # Re: What you see is not what you get
Posté par lmg HS (site web personnel) . Évalué à 8.
Le C++ est clairement un langage que plus on le connait, plus on sait à quel point on ne sait pas grand chose. Mais ce n'est pas très grave. Typiquement, ce genre d'optimisation, ne nous change pas vraiment la vie tant que l'on n'est pas à vouloir optimiser très précisément ce qu'il se passe.
De plus, et c'est particulièrement vrai depuis le C++11, toutes les choses dans la norme ne sont pas directement pour nous, mais pour des concepteurs de bibliothèques qui pourront nous peaufiner des bibliothèques aux petits oignons qu'il ne nous restera plus qu'à utiliser.
Tous les utilisateurs de Python n'ont pas forcément conscience des dictionnaires qui sont omniprésents et qui expliquent tout ce qu'il est possible de faire avec le langage, en bien ou en mal. Et pourtant, ils n'ont aucun mal à s'en servir. Ici, ce n'est guère différent.
[^] # Re: What you see is not what you get
Posté par jcelerier . Évalué à 3.
Il y a un secret (en tout cas avec GCC et Clang) pour rendre les erreurs de templates pas trop insurmontables.
95% du temps, la ligne d'ou provient l'erreur dans ton code est indiquée par la ligne d'erreur "Required from here" de GCC qui va être au début de l'erreur; si tu utilises QtCreator par exemple tu peux double-cliquer dessus et ça t'y emmène dans le code.
Bjarne lui même a dit qu'il devait se référer au standard pour être sûr de tout.
Ce sont surtout les auteurs de bibliothèques qui les utilisent.
Regarde par exemple le code de Eggs.variant ou Boost.Hana
[^] # Re: What you see is not what you get
Posté par Oliver (site web personnel) . Évalué à 4.
Bonjour,
Merci pour tes encouragements.
Je ne sais pas si cela peut te rassurer, mais, nous non plus, nous ne connaissons pas grand chose à la norme C++.
Pour l’anecdote, un journaliste avait demandé à Bjarne Stroustrup, le fondateur du C++ et membre assidu du comité de normalisation du C++, quel était son niveau de connaissance de la norme. Il avait alors répondu 6 sur 10. Personnellement, je pense que la plupart des personnes qui se disent expert en C++ doivent avoir un niveau de connaissance inférieur à 20 %.
Pour te rassurer, sache que les membres du comité de normalisation font très attention à ne pas rajouter de complexité inutile au langage. Si une fonctionnalité peut entraîner de mauvaises pratiques de code, alors elle est retravaillée ou écartée. C’est par exemple, le cas du Variable Length Array présent dans le langage C, mais pas dans le C++. C’est aussi pourquoi certaines fonctionnalités très attendues sont encore et encore repoussées, comme
import std.string;
qui devrait devenir une alternative à#include <string>
. La norme C++ se bonifie avec le temps, plus intuitive, moins de pièges.Nous allons continuer nos petites dépêches pédagogiques afin de démystifier les
template
. Objectif pour le 30 novembre 2017 à minuit : ne plus en avoir peur.N’hésite pas à nous donner un coup de main ;-)
(surtout si tu as envie de dessiner)
Commentaire sous licence Creative Commons Zero CC0 1.0 Universal (Public Domain Dedication)
[^] # Re: What you see is not what you get
Posté par David Marec . Évalué à 3.
On (re)tombera facilement dans les pièges comme celui présenté par cette dépêche où l'ordre d'évaluation d'un stream de la dépêche précédente. Il faut juste savoir s'en souvenir quand le comportement n'est pas celui que l'on attendais. Le pire étant une
optimisation
qui provoque un comportement inattendu dans un cas inattendu.Savoir qu'il y a des pièges, pleins, partout et reconnaître que l'on peut ou que l'on va se prendre les pieds dans le tapis à un moment ou à un autre, c'est peut ça « être expert ».
Un autre piège serait de croire qu'un
std::vector
fait la même chose; sauf que celui-ci va allouer ses éléments sur le tas, alors que le VLA va les allouer sur la pile.L'intégration des VLA n'est-il pas toujours en discussion ?
--
Une dépêche pour définir une échelle de compétences en C++ ?
[^] # Re: What you see is not what you get
Posté par lmg HS (site web personnel) . Évalué à 2.
On a failli, et puis finalement non. Je n'ai pas suivi ce sujet -> ne pas me demander pourquoi.
[^] # Re: What you see is not what you get
Posté par Oliver (site web personnel) . Évalué à 2.
Pour information, VLA est le nom pour le langage C. En C++, nous devrions dire Runtime-Sized Arrays.
Mais bon, VLA est le nom le plus connu. Même les membres du comité de normalisation du C++ disent VLA.
Du coup, moi aussi je dis VLA (ne multiplions pas les noms qui désignent un peu près la même chose).
En 2013, le comité a d'abord adopté les VLA (appelé Runtime-sized arrays with automatic storage duration)
https://isocpp.org/blog/2013/04/n3639-runtime-sized-arrays-with-automatic-storage-duration
Et puis finalement, retour en arrière. Il me semble que les VLA n'ont pas été intégrés car le risque est trop grand d'allouer plus d'espace que l'espace libre disponible dans la pile d'appel.
Par contre, le
std::dynarray
a bien été adopté pour C++14 et permet aussi d'allouer sur la pile.Ce qu'il faut retenir, c'est que le comité de normalisation du C++ a tendance à ne pas intégrer ce qui est bugogène (error prone).
Commentaire sous licence Creative Commons Zero CC0 1.0 Universal (Public Domain Dedication)
[^] # Re: What you see is not what you get
Posté par Oliver (site web personnel) . Évalué à 1. Dernière modification le 12 décembre 2016 à 19:32.
Après une petite recherche, j'ai trouvé ce chapitre :
https://wg21.link/p0096#recs.removed
Donc ni les VLA, ni les
std::dynarray
, n'ont été intégrés au final dans C++14 (ni d'ailleurs dans C++17).Notons que
std::optional
a été adopté pour C++14, puis retiré, et en ce moment il est à nouveau adopté pour C++17, malgré de vives polémiques… Une dépêche sur ce sujet est prévue pour l'année prochaine ;-)Commentaire sous licence Creative Commons Zero CC0 1.0 Universal (Public Domain Dedication)
[^] # Re: What you see is not what you get
Posté par Oliver (site web personnel) . Évalué à 1.
Je modifie mon dernier paragraphe :
Notons que
std::optional
a été adopté pour C++14, puis retiré, et en ce moment il est à nouveau adopté pour C++17, malgré de vives polémiques… Voir à ce propos le très bon journal de Guillaum (septembre 2016, CC BY-SA 4.0) :http://linuxfr.org/users/guillaum/journaux/gestion-de-l-erreur-c-std-optional
Une dépêche plus complète sur ce sujet est prévue pour l'année prochaine ;-)
Commentaire sous licence Creative Commons Zero CC0 1.0 Universal (Public Domain Dedication)
[^] # Re: What you see is not what you get
Posté par lmg HS (site web personnel) . Évalué à 2.
NB: il proposait d'utiliser
optional<>
pour faire plus que la sémantique portée par son nom : il proposait de l'utiliser comme lestd::expected<>
en cours d'élaboration. Cf mes commentaires dans cette autre dépêche.[^] # Re: What you see is not what you get
Posté par Nicolas Boulay (site web personnel) . Évalué à 4.
La taille de la pile est fixe donc, forcément, on peut débordé. C'est exactement le même problème en C. De mémoire, cette pile fait 8 Mo.
D'ailleurs, pourquoi ne pas allouer cette mémoire "ailleurs" tout en étant en RAII ? De toute façon mettre des objets de tailles variables dans la pile est une très mauvaise idée. (je me rend compte que cela doit revenir à utiliser un vector<> dans la pile)
"La première sécurité est la liberté"
[^] # Re: What you see is not what you get
Posté par whity . Évalué à 2.
La seule bonne raison de le faire est quand tu :
- es sûr que l’allocation tiendra dans la pile
- ne veux pas d’allocation dynamique
Dans un système critique, par exemple, les allocations dans le tas durant l’exécution peuvent être interdites. Du coup, allouer un tableau tampon temporaire dans la pile, si tu es sûr qu’il tient, a du sens.
Cela dit, je ne sais plus si les VLAs de C permettent de contrôler la taille du tableau avant l’allocation. Il me semble que non, et que c’est justement pour ça que c’est une mauvaise idée.
Mes commentaires sont en wtfpl. Une licence sur les commentaires, sérieux ? o_0
[^] # Re: What you see is not what you get
Posté par Nicolas Boulay (site web personnel) . Évalué à 5.
C'est bien plus propre d'allouer un tableau statique de la taille maximum, cela évite une mauvaise surprise. Même en cas de mémoire contrainte, il faut bien pouvoir allouer la mémoire maximum, autant le faire au lancement du programme. Si tu utilises ce patterns souvent, je ne vois pas comment tu peux prouver que la stack est assez grande. A mon avis, ce genre d'appel est interdit en MISRA C.
"La première sécurité est la liberté"
[^] # Re: What you see is not what you get
Posté par David Marec . Évalué à 8.
Ce n'est pas spécifié et varie selon l'OS, l'architecture, la chaîne de compilation (
setrlimit
) etc.Donc, ça dépend.
Alors forcement : « ça dépend », ça dépasse.
-- promptly disappeared in a puff of smoke
[^] # Re: What you see is not what you get
Posté par Nicolas Boulay (site web personnel) . Évalué à 3.
De mémoire, tu peux le fixer sous Linux, même cela fait parti du binaire sous Windows, mais ce n'est pas conseillé de la changer (mais je ne sais plus pourquoi).
Oui, tu peux la changer, mais en pratique, tu ne le fais pas.
Donc, moins il y a des choses dans la pile, mieux tu te portes. D'ailleurs, une grosse pile ailleurs dans le tas, pourrait faire l'affaire. Ocaml utilise une pile de 256Ko comme première zone mémoire. L'allocation est ultra rapide, justement parce que c'est une pile. Le GC bosse ensuite, si la donné sort d'un scoop.
On pourrait imaginer une allocation RAII, qui utilise une autre pile selon le même genre de fonctionnement.
"La première sécurité est la liberté"
[^] # Re: What you see is not what you get
Posté par David Marec . Évalué à 2. Dernière modification le 13 décembre 2016 à 11:57.
Oui, c'est surement la principale raison de l'existence des VLA.
( et d' alloca)
Bah, dans le cas de la pile, ça ne doit pas coûter plus cher que la restauration du
frame pointer
.Gros bide avec ma blagounette.
[^] # Re: What you see is not what you get
Posté par Nicolas Boulay (site web personnel) . Évalué à 3.
Si, parce que c'est un déplacement, la pile est vidé, il faut donc mettre à jour les pointeurs.
"La première sécurité est la liberté"
[^] # Re: What you see is not what you get
Posté par David Marec . Évalué à 3.
Comme C++ permet assez facilement d'utiliser un allocateur maison, j'ai eu à faire ce genre de chose: pré-allocation de blocs et une gestion de piles sur ceux-ci.
Pour un truc plus abouti, les TBB d'Intel sont une implémentation intéressante.
[^] # Re: What you see is not what you get
Posté par lasher . Évalué à 4.
J'ai peut-être raté quelque chose, mais je ne suis même pas sûr que la spec de C99 (ou C11) parle de pile. Elle parle juste de VLA. La raison étant qu'entre le moment où C a été créé, et où il a été normalisé, certains éditeurs de compilateurs C ont porté le langage sur des machines qui n'avaient pas à proprement parle de pile. Et ces machines étaient utilisées en production. Du coup, lors de sa normalisation, le comité ne pouvait pas assumer l'existence d'une pile ou d'un tas (même si c'est comme ça que 99,9% des implémentations sont/étaient faites).
Concernant la taille de la pile, ça dépend de l'archi (genre sur IA-64, je crois que la pile était ridiculement petite, genre 15kio, entre autres parce qu'il y avait 128 registres entiers et 128 registres flottants, et même en utilisant ulimit ou autres commandes, on ne pouvait pas la faire trop grossir).
[^] # Re: What you see is not what you get
Posté par David Marec . Évalué à 2.
En effet, la norme ne spécifie que sur la portée d'un identifiant (« scope ») et sa durée de vie (« storage duration »).
Les VLA sont même un cas particulier de la catégorie « automatic storage duration », ils ne vivent pas au delà du « bloc » de leur déclaration.
Si la suite est du détail d'implémentation, le fond de l'affaire reste le même: déclarer un VLA, ce n'est pas «allouer dynamiquement» un tableau de taille variable. La durée de vie n'est pas la même.
[^] # Re: What you see is not what you get
Posté par Nicolas Boulay (site web personnel) . Évalué à 3.
Tu parles peut être d'une gestion hardware d'une pile. Sur un processeur RISC, il n'y pas forcément de gestion hardware spécifique, il y a juste une convention d'usage de registre. Mais je ne vois pas comment tu peux fonctionner sans pile du tout. Il faut bien gérer les adresses de retour des fonctions.
Cela m'étonnerait beaucoup. ça limiterait beaucoup trop la profondeur d'appel de fonction (appel récursif par exemple) (2Mb ici : https://blogs.msdn.microsoft.com/slavao/2005/03/19/be-aware-ia64-stack-size/ ). Comme rappelé dans le poste du blog, avoir une stack minuscule peut être utile si on utilise plein de threads (4ko par exemple à condition de ne rien allouer dedans).
"La première sécurité est la liberté"
[^] # Re: What you see is not what you get
Posté par pulkomandy (site web personnel, Mastodon) . Évalué à 2.
Il est aussi possible d'allouer la pile de façon dynamique: on mappe 4Ko au départ, et juste au-dessus, une page interdite d'accès pour le thread en question. Quand le thread utilise ses 4Ko, il finit par essayer d'écrire dans ce bloc protégé. Cela déclenche une exception (segmentation fault) qui est interceptée, le noyau peut alors ajouter 4Ko de mémoire supplémentaire, et ainsi de suite.
Il me semble que cette stratégie est utilisé par iOS, entre autres.
[^] # Re: What you see is not what you get
Posté par Nicolas Boulay (site web personnel) . Évalué à 3.
Cela ne marche pas comme ça sur windows et linux. La page de protection existe, et cela finit en crash. La pile ne peut jamais grossir. Pour iOS cela m'étonnerait aussi.
"La première sécurité est la liberté"
[^] # Re: What you see is not what you get
Posté par pulkomandy (site web personnel, Mastodon) . Évalué à 4.
J'ai retrouvé ma source: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html
Par contre (et en fait, c'est logique), il y a quand même une limite qui est qu'on finit forcément par atteindre l'adresse logique d'une autre allocation. Donc la stack n'est pas illimitée pour autant. Par défaut la limite est de 1MB pour le thread principal et de 512K pour les autres.
[^] # Re: What you see is not what you get
Posté par Nicolas Boulay (site web personnel) . Évalué à 2.
Le tableau présente les valeurs par défaut, tout est réglable.
"La première sécurité est la liberté"
[^] # Re: What you see is not what you get
Posté par lasher . Évalué à 3.
Oui, quelque chose comme decrit ici: http://electronics.stackexchange.com/questions/61946/what-does-it-mean-for-a-cpu-to-support-a-stack
Honnetement je ne me souviens plus exactement. Ce dont je me souviens tres bien c'est que la taille de la pile pour IA-64 etait beaucoup plus petite que pour x86-64. Et par contre, oui, j'ai oublie de mentionner que je parlais de la pile pour des threads, et pas du processus general (un gros oubli, pardon).
[^] # Re: What you see is not what you get
Posté par David Marec . Évalué à 1.
C'est ce que fait
std::vector
, non ?Une entête allouée sur la pile dont les éléments sont alloués sur le tas.
[^] # Re: What you see is not what you get
Posté par Nicolas Boulay (site web personnel) . Évalué à 3.
Oui, c'est ce que je disais à la fin du commentaire.
"La première sécurité est la liberté"
[^] # Re: What you see is not what you get
Posté par Troy McClure (site web personnel) . Évalué à 8.
J'en profite pour dire merci pour ces dépêches que je trouve à la fois intéressantes et bien écrites, bravo aux contributeurs. Il fut une époque où je me considérais pas trop mauvais en c++ mais c'était il y a fort longtemps (bien avant le c++11) et ces articles tombent vraiment à point pour redonner envie de sortir de ma zone de confort c++98 :)
# euh ?
Posté par Nicolas Boulay (site web personnel) . Évalué à 6.
Quand j'ai commencé à aimer C++, j'ai eu vent de toutes les subtilités des constructeurs. Et j'en avais conclus que c'était imbitable pour des fonctionnalités aussi basiques. Et je suis retourné au C et Perl.
Source ? Vous pensez à quoi ?
"La première sécurité est la liberté"
[^] # Re: euh ?
Posté par jcelerier . Évalué à 1.
Par exemple le compilateur peut réordonner le flot des instructions un peu comme bon lui semble tant que ça fait le bon résultat à la fin : https://godbolt.org/g/bvePqx
[^] # Re: euh ?
Posté par Nicolas Boulay (site web personnel) . Évalué à 5.
Oui mais en quoi est-ce différent pour le C ?
"La première sécurité est la liberté"
[^] # Re: euh ?
Posté par David Marec . Évalué à 2.
Je trouve en effet que l'exemple donnée d' élision du constructeur par copie par le compilateur est le contraire de ce qui est attendu par le « code source ». Le code source demande explicitement à la construction par copie de modifier une donnée de l'objet source. Ce que le compilateur va ignorer.
C'est clairement une chausse-trappe.
[^] # Re: euh ?
Posté par Nicolas Boulay (site web personnel) . Évalué à 5.
Comme dit dans la phrase juste après que j'ai copié, le C++ semble définir une sémantique "attendue" pour le constructeur, qui permet des optimisations. Par contre, les compilateur ne contrôle en rien cette attente.
Pour un designer de langage, c'est la facilité : une faiblesse de génération est compensé par une optimisation qui fait des hypothèses sur le code, mais il est impossible de vérifier que le code respecte effectivement l’hypothèse.
Il manque définitivement un outil qui vérifie ce genre d'erreur, c'est pas humainement possible de tout se rappeler.
Le C tient en une centaine de page d'explication. Ocaml a peine plus, si on regarde sa doc en ligne.
A voir, si le C++ "moderne" peut se réduire à moins de 1000 pages…
"La première sécurité est la liberté"
[^] # Re: euh ?
Posté par whity . Évalué à 5.
En fait le truc, c’est que dans la vraie vie, tu t’en tapes. C’est à dire que le « problème » n’intervient que si le constructeur (de copie) a des effets de bord. Or c’est une très mauvaise pratique qu’il en ait.
On pourrait imaginer dans le futur pouvoir « marquer » de tels constructeurs, pour que le compilateur n’applique pas ces optimisations dessus. Toutefois, le fait que ça ne soit pas le cas montre surtout que le besoin est plutôt inexistant.
Mes commentaires sont en wtfpl. Une licence sur les commentaires, sérieux ? o_0
[^] # Re: euh ?
Posté par Nicolas Boulay (site web personnel) . Évalué à 3.
ok. Mes lectures remontent à loin, mais dans mon souvenir, il fallait faire attention à la manière de manipuler les objets pour éviter des recopies inutiles, par exemple. Il fallait ruser pour éviter des appels de constructeur en cascade. Mais c'est vrai que ce n'est pas le sujet de l'article.
"La première sécurité est la liberté"
[^] # Re: euh ? [spoiler]
Posté par David Marec . Évalué à 1.
D'après ce que j'ai compris, c++17 va garantir l'élision par défaut.
Du coup, pour obtenir la copie d'une source, obtenue en modifiant une ou plusieurs données de la source, il faudra penser à changer de méthode.
Pour être franc, je n'ai pas de cas comme celui-là en tête.
[^] # Re: euh ?
Posté par Aris Adamantiadis (site web personnel) . Évalué à 6.
Au plus je lis ces séries d'articles, au plus ça confirme le sentiment que j'avais que C++ n'est pas pour moi. Beaucoup trop verbeux et trop compliqué, et les histoires de constructeur de copie sont très symptomatiques de tous les pièges que réserve ce gros langage.
[^] # Re: euh ?
Posté par Zenitram (site web personnel) . Évalué à 2.
En pratique tu ne tombes pas dans les pièges mentionnés, car tu ne joues pas à ça (un constructeur de copie est utile pour copier, pas pour modifier; quand une commande modifie, on n'a pas pour habitude de l'imbriquer n'importe comment).
Ici, ce sont des cas d'école, fait pour s'amuser et voir nos limites en connaissance, pas pour l'usage de tous les jours.
Donc c'est une mauvaise excuse de se baser la dessus pour dire que ce n'est pas fait pour soit.
(et si tu veux jouer, je pense qu'on pourrait faire les mêmes dépêches sur les "pièges" tarabiscotés avec que n'importe quel langage ayant un peu d'âge, bref un truc ayant vécu et dont ont a une histoire, ce qui arrive à n'importe quel langage utilisé)
[^] # Re: euh ?
Posté par Nicolas Boulay (site web personnel) . Évalué à 2.
Oui, mais quand on risque un crash ou une différence de sémantique par rapport au contenu du code, c'est quand même du lourd (plus que des trucs ésotérique dans les regex perl par exemple).
"La première sécurité est la liberté"
[^] # Re: euh ?
Posté par barmic . Évalué à 5.
Moi en pratique, je n'ai pas encore véritablement compris le constructeur de déplacement donc les cas tordus comme expliqué au dessus, c'est vraiment de la théorie…
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: euh ?
Posté par Aris Adamantiadis (site web personnel) . Évalué à -1.
J'ai bien dis que c'était une confirmation. Chaque article sur le C++ que je lis sur LinuxFR ce mois-ci en est une. Et les constructeurs de copie ont toujours été un problème dans C++, au delà du petit cas pathologique expliqué dans cet article.
[^] # Re: euh ?
Posté par lmg HS (site web personnel) . Évalué à 3.
Quel est le problème avec les constructeurs de copie ? Qu'il faut savoir quand il faut les définir et quand il faut les interdire ? Ce n'est franchement pas très compliqué.
C'est avant tout un problème de design : savoir distinguer les entités des valeurs, problème qui se pose aussi avec d'autres langages sous d'autres formes: ainsi, cf. les values objects qui ne doivent pas être dérivés en Java (p.ex. un point coloré n'est pas un point, mais il est composé d'un point—cf Effective Java v2).
Certes en C++, un mauvais design va résulter en un plantage plus vite qu'avec d'autres langages qui vont juste se contenter de nous laisser écrire du code susceptible de produire des résultats aberrants.
# Typo
Posté par Marc (site web personnel) . Évalué à 3.
Merci pour les dépêches, c'est toujours intéressant d'apprendre ces détails :)
PS: je ne suis pas super fan des illustrations de cette série de dépêches…
[^] # Re: Typo
Posté par xcomcmdr . Évalué à 2.
se serait/ce serait/g
"Quand certains râlent contre systemd, d'autres s'attaquent aux vrais problèmes." (merci Sinma !)
[^] # Re: Typo
Posté par Benoît Sibaud (site web personnel) . Évalué à 3.
Corrigé, merci.
[^] # Re: Typo
Posté par Oliver (site web personnel) . Évalué à 1.
Merci pour la coquille ;-)
As-tu des idées pour les illustrations ?
On ne sait pas dessiner (on débute) et on aimerait illustrer chaque dépêche avec un petit truc sympa.
Merci de nous aider à améliorer cet aspect esthétique des dépêches C++.
Commentaire sous licence Creative Commons Zero CC0 1.0 Universal (Public Domain Dedication)
[^] # Re: Typo
Posté par Marc (site web personnel) . Évalué à 1.
Je m'attendais à cette réponse :) Non, je n'ai pas d'idée pour améliorer les illustrations. Je pourrais contribuer, c'est vrai, mais ça ne serait pas mieux. En l'état, je ne suis pas convaincu que ces illustrations apportent au contenu qui se suffit très bien (clair, précis, complet).
[^] # Re: Typo
Posté par Oliver (site web personnel) . Évalué à 3.
Espérons que d'autres lecteurs apprécient ces dessins car je passe beaucoup de temps à les retoucher sur GIMP pour m'assurer que l'arrière plan est bien blanc, réfléchir aux textes, les retoucher… c'est du temps en moins sur la dépêche.
Sur la prochaine dépêche, on demandera aux lecteurs de LinuxFr.org de donner leur avis (du moins, ceux qui ont un compte).
Commentaire sous licence Creative Commons Zero CC0 1.0 Universal (Public Domain Dedication)
[^] # Re: Typo
Posté par Zenitram (site web personnel) . Évalué à 1.
Au moins une personne apprécie.
l'arrière plan devrait être transparent afin de s'adapter au fond de la CSS utilisée.
[^] # Re: Typo
Posté par whity . Évalué à 5.
Au moins deux.
Si ta css met un fond noir, le dessin sera illisible. De manière générale, soit tu fixes l’arrière plan et l’avant plan, soit tu n’en fixes aucun, mais en fixer un seul sur les deux, c’est très pénible (quiconque a déjà utilisé un thème « sombre » pour naviguer maudit les dev webs qui ne prennent pas cette précaution élémentaire).
Mes commentaires sont en wtfpl. Une licence sur les commentaires, sérieux ? o_0
[^] # Re: Typo
Posté par Oliver (site web personnel) . Évalué à 3.
Très bonne idée l'arrière plan transparent.
Pour les fichiers PNG : Je vais laisser une aura blanche autours des traits noirs, et ce blanc va s'estomper (être de plus en plus transparent) au fur et à mesure de l'éloignement du trait noir. J'avais déjà fait cela pour du WebP, mais malheureusement Firefox ne prend pas encore en charge ce format :-(
https://github.com/cpp-frug/materials/blob/gh-pages/images/README.md#c17-sauve-une-%C3%A9coli%C3%A8re
Le choix va aussi dépendre de la taille du fichier. Actuellement j'arrive à produire des fichiers de quelques dizaines de Ko. Il ne faudrait pas que cela deviennent des centaines de Ko… J'ai été traumatisé par mon mobile qui essayait désespéramment de télécharger des images dans les transports parisiens !
Pour les fichiers SVG : Je mets, en arrière plan, un rectangle blanc de la taille de l'image avec opacity=30%. Et quand j'y pense, je rajoute un fin trait blanc autour des caractères…
Si vous avez des astuces, des techniques, des conseils… pour faire cela, merci de partager ;-)
Merci pour le retour d'expérience
Commentaire sous licence Creative Commons Zero CC0 1.0 Universal (Public Domain Dedication)
# Initialisation des variables
Posté par David Marec . Évalué à 3.
Pourquoi pas
dans ce cas précis, ou même
pour un usage plus général, quelque soit la portée de la variable.
[^] # Re: Initialisation des variables
Posté par Oliver (site web personnel) . Évalué à 2.
Oui, c’est une très bonne idée.
Il faut absolument que tu nous donnes un coup de main pour nous aider en amont.
=> Lors de la rédaction des dépêches.
Rendez-vous sur cette page :
http://linuxfr.org/redaction/news/c-point-de-rassemblement-des-contributeurs-aux-depeches-c
Et ne vend pas trop la mèche sur la prochaine dépêche, chut c’est une surprise ;-)
Commentaire sous licence Creative Commons Zero CC0 1.0 Universal (Public Domain Dedication)
[^] # Re: Initialisation des variables
Posté par barmic . Évalué à 3.
Je croyais que c'était pas garantie ce comportement ?
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: Initialisation des variables
Posté par David Marec . Évalué à 2.
Lequel ?
Pour l'initialisation des variables « non locale »:
Pour l'initialisation par une liste, c'est plus touffu:
La norme que j'ai lue se trouve sur openstd
[^] # Re: Initialisation des variables
Posté par gbdivers . Évalué à 1.
N3092 date de 2010, mais il contient du C++11. Ca doit etre un draft avant soumission a l'ISO. Je sais pas si c'est le draft final.
Pour ceux qui sont interesse par le draft "courant" (donc avec les ajouts du C++17), cf isocpp.org : N4618 http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/n4618.pdf
# questions sur les constructeurs et les vecteurs
Posté par MCMic (site web personnel) . Évalué à 3.
Bonjour,
J’en profite parce que je suis tombé deux trois fois sur des soucis bizarres en ajoutant l’utilisation d’objets et de vecteurs de ceux-ci dans le code de Lugaru (dépêche en cours de rédaction).
En gros ya des classes d’objet mal foutues et donc pas copiables, genre parce que ça alloue des trucs dans le constructeur et les libère dans le destructeurs, et copie = double destruction.
En faisant un vecteur de ça et un push_back(Object(args)), j’obtiens un crash car ça crée un objet, le copie puis détruit l’original.
Je me dis qu’il suffit de remplacer par emplace_back(args), mais non, crash quand même, emplace_back appelle un destructeur je comprends pas pourquoi.
La solution que j’ai trouvé est de faire un vecteur de unique_ptr à la place.
En gros mes questions :
- Comment fonction emplace_back, pourquoi il appelle le destructeur
- Comment expliciter qu’une classe n’est pas copiable ?
- Peut-on faire des vecteurs de classe non-copiable ?
Note : le code en question est en c++11 pour l’instant.
[^] # Re: questions sur les constructeurs et les vecteurs
Posté par lmg HS (site web personnel) . Évalué à 1. Dernière modification le 15 décembre 2016 à 11:48.
Si ta classe possède des ressources, il est nécessaire de prévoir comment tu la dupliques. Si tu autorises alors il faut définir le constructeur de copie et l'opérateur d'affectation par copie (plus éventuellement les équivalents en déplacement si tu veux en profiter), sinon, tu interdis. Comment? Réponse dans la FAQ: http://cpp.developpez.com/faq/cpp/?page=Semantique-de-copie
Dans tous les cas, il te faut un accès au code de ces classes pour spécifier comment la copie et le déplacement se font (ou pas).
[^] # Re: questions sur les constructeurs et les vecteurs
Posté par whity . Évalué à 2.
emplace_back construit un objet avec les paramètres qu’on lui donne. Mais c’est une construction classique, ce que ça évite, c’est de construire l’objet puis le copier, en deux étapes. Lorsque qu’un vecteur réalloue, il copie puis détruit (ou il déplace).
Pour marquer qu’un objet n’est pas copiable, il faut « deleter » son constructeur de copie, ainsi que l’opérateur d’affectation :
Pour faire des vecteurs de classe non-copiable, il faut que l’objet soit movable, c’est à dire, qu’il ait un constructeur de mouvement défini. Une autre solution est de passer par un unique_ptr, comme tu l’as fait.
Mes commentaires sont en wtfpl. Une licence sur les commentaires, sérieux ? o_0
[^] # Re: questions sur les constructeurs et les vecteurs
Posté par MCMic (site web personnel) . Évalué à 2.
Merci pour les infos (Et à lmg HS au dessus).
Je pense que je supprimerais les constructeurs par copie et que j’utiliserai unique_ptr alors, c’est plus simple.
Je sais que les classes devraient être corrigées mais en général c’est des trucs que je compte nettoyer plus tard de toutes façons, je veux juste pouvoir les utiliser dans des vecteurs en attendant.
[^] # Re: questions sur les constructeurs et les vecteurs
Posté par David Marec . Évalué à 3.
emplace
ajoute l'élément qu'il va construire à la volée avec les arguments donnés.C'est le
vector
qui faire des copies lorsqu'il va devoir grossir; donc, tout reconstruire ailleurs.ex:
Diminuer le
reserve
( voire l'enlever) fait tout planter.[^] # Re: questions sur les constructeurs et les vecteurs
Posté par lmg HS (site web personnel) . Évalué à 2.
Je dirais même: directement dans la destination (déjà allouée). Comme le nom l'indique, cela fait de la construction placée avec le
new
de placement. Mais bon. C'est du détail arcanique. Il est plus important de comprendre dans un premier temps comment on écrit des classes valeurs et savoir distinguer les entités (celles qui n'ont pas besoin d'/ne doivent pas être copiées)—David illustre un cas où le problème peut se poser, celui que tu observes.# Lien cassé
Posté par Matthieu Moy (site web personnel) . Évalué à 1.
Le lien « constructeur nommé » est cassé (lien relatif au lieu d'absolu). Le lien correct est :
http://cpp.developpez.com/faq/cpp/?page=Les-constructeurs#Qu-est-ce-que-l-idiome-du-constructeur-nomme-Named-Constructor
Si un modérateur passe par là …
[^] # Re: Lien cassé
Posté par Benoît Sibaud (site web personnel) . Évalué à 2.
Corrigé, merci.
# Test avec le compilateur CL de Microsoft en toolset vc140
Posté par Mathieu Schroeter (site web personnel, Mastodon) . Évalué à 3.
Hello,
Pour le fun j'ai testé le code suivant avec le compilateur CL de Microsoft en toolset vc140.
Résultats :
A priori CL ne sait pas faire l'élision aussi efficacement que g++ et clang++ dans ce cas de figure.
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.