std::forward, seems valid pour du Perfect Forwading (Move au lieu de Copy)
Oui c'est logique, tu passe d'un String Literal à un std::string, donc création d'un objet std::string. Ensuite l'objet membre "name" utilise le constructeur par déplacement de l'objet précédemment créé.
Ce qu'il faut savoir c'est qu'avec les templates c'est le même principe que les fonction inline. Au final le compilateur peut optimiser grandement le tout, beaucoup plus bas niveau. Tu peux considérer le résultat en version optimisé, comme s'il n'y avait pas de création d'objet temporaire.
GZE, un moteur multiplateforme, adapté pour de la 2D, 3D et création de logiciels.
Oui, c'est ce que je dis C'est normal car tu passe d'un String Literal à un std::string, si tu passe directement un std::string, il n'y aura pas d'objet temporaire.
GZE, un moteur multiplateforme, adapté pour de la 2D, 3D et création de logiciels.
Oui c'est logique, tu passe d'un String Literal à un std::string, donc création d'un objet std::string. Ensuite l'objet membre "name" utilise le constructeur par déplacement de l'objet précédemment créé.
"
J'ai un doute :
je dirais plutôt que c'est d'abord un constructeur par déplacement qui est utilisé pour créer un objet temporaire std::string.
Ce qui nous intéresse, parmi les constructeur: string (const char* s); string (string&& str) noexcept;
a.setname("openclassromms");
"openclassromms" est un String Literal, T est un std::string
void setName(T&& newName)
On ne peut passer directement une référence, on doit appeler le constructeur: string (const char* s); D'où l'objet temporaire, ensuite il est passé par référence sur Rvalue &&
Après:
name = std::forward<T>(newName);
Un peu comme le ferait std::Move, il va appeler le constructeur par déplacement : string (string&& str) noexcept;
Mais bon, c'est le genre de micro optimisation qui ne serait pas nécessaire si std::string suivrait le principe de COW (Copy On Write), ou bien tout autre objet qui utilise le compte de références.
GZE, un moteur multiplateforme, adapté pour de la 2D, 3D et création de logiciels.
> Mais bon, c'est le genre de micro optimisation qui ne serait pas nécessaire si std::string suivrait le principe de COW (Copy On Write), ou bien tout autre objet qui utilise le compte de références.
C'est la grosse merde le COW sur std::string, ce n'est pas pour rien que les implémentations l'ont supprimé (et aussi parce que la norme l'empêche, mais c'est un détail). Le COW oblige des conditions et une copie pour chaque fonction susceptible de modifier la chaîne, y comprit la version non const de operator[], ce qui arrive très vide. Le COW cause aussi des problèmes avec les itérateurs qui peuvent référencer le mauvais objet suite à un appel de operator[].
> "openclassromms" est un String Literal, T est un std::string
T est un char const(&)[15]. Les templates ne font jamais de transformation de type et cela n'aurait aucun sens avec le perfect forwarding.
Oui, ça plus de sens
Mais, j'ai l'impression que ça change rien car il n'y a pas de constructeur par déplacement pour un String Literal dans std::string?
jo_link_noir a écrit:
C'est la grosse merde le COW sur std::string, ce n'est pas pour rien que les implémentations l'ont supprimé (et aussi parce que la norme l'empêche, mais c'est un détail). Le COW oblige des conditions et une copie pour chaque fonction susceptible de modifier la chaîne, y comprit la version non const de operator[], ce qui arrive très vide. Le COW cause aussi des problèmes avec les itérateurs qui peuvent référencer le mauvais objet suite à un appel de operator[].
Je crois que tu fais une misconception du principe COW, car s' il y a qu'une instance (dans la plupart des cas) il n'y aura pas de copie.
Aussi, en terme de performances tu peux avoir plus d'optimisations, si tu prend le cas ci-dessus, std::string doit faire une copie de la chaine pour permettre l'écriture, dans le cas du COW, il n'y a pas besoin de copier la chaine, tant qu'elle est utilisé en lecture.
Tu peux aussi séparer les accès lectures/écritures, ce qui au final ne fait pas plus de condition qu'un std::string en lecture
- Edité par Maeiky 18 mars 2019 à 19:46:07
GZE, un moteur multiplateforme, adapté pour de la 2D, 3D et création de logiciels.
Sans trop être hors sujet, personnellement je ne considère pas ça comme une solution, ce n'est simplement pas générique. Vous êtes vite sur la gâchette pour tout jeter à la poubelle, j'ai à peine évoquer une partie des possibilités.
Pour avoir fait ma propre implémentation, il est possible de faire des références de sous string, dans ce cas par exemple, un appel de substr ce fait sans même de copie (donc gratuit), ce qui est beaucoup plus intéressant en terme de performances.
Après c'est à vous de voir, je voulais simplement dire que le principe du move ne s'applique pas à tout.
GZE, un moteur multiplateforme, adapté pour de la 2D, 3D et création de logiciels.
Mais, j'ai l'impression que ça change rien car il n'y a pas de constructeur par déplacement pour un String Literal dans std::string?
Par définition, un constructeur par mouvement prend le même type, donc of course qu'il n'y a pas un tel constructeur, sinon ca ne serait pas un constructeur par mouvement.
Maeiky a écrit:
Je crois que tu fais une misconception du principe COW
[...]
Vous êtes vite sur la gâchette pour tout jeter à la poubelle, j'ai à peine évoquer une partie des possibilités.
Non, tu n'as pas compris les arguments de joe_link_noir (qui ne sont d'ailleurs pas de lui, ce sont des problèmes connus du COW). On n'a pas besoin de toi pour nous expliquer les avantages et défauts du COW, c'est un pattern qui est utilisé depuis des dizaines d'années et qui est bien connu.
C'est une solution qui est utilisé dans Qt par exemple et ils ont fait ce choix en parfaite connaissance des problèmes. (Qt n'est pas critique sur ce point en termes de performances, c'est pour cela qu'ils ont fait ce choix). Au contraire, la lib standard est critique sur les performances ("on ne paie pas pour ce qu'on utilise pas") et ils ont donc fait un choix différents.
Libre a toi de faire tes propres choix sur tes projets, mais c'est bof bof de critiquer le choix fait sur d'autres projets (la lib standard) sans connaitre les tenants et aboutissants de chaque projet.
Maeiky a écrit:
Pour avoir fait ma propre implémentation, il est possible de faire des références de sous string, dans ce cas par exemple, un appel de substr ce fait sans même de copie (donc gratuit), ce qui est beaucoup plus intéressant en terme de performances.
Donc en gros, tu as implémenté std::string_view...
Encore une fois, libre a toi de faire les choix que tu veux sur tes propres projets. Mais à moins que tu as trouvé une implémentation super génial qui peut gérer dynamiquement 2 systèmes d'ownership différents dans la même classe (RAII pour std::string, pas d'ownership pour string_view) sans surcout, ton implémentation n'est pas acceptable pour la lib standard.
Maeiky a écrit:
ce n'est simplement pas générique.
Tu as une conception limitée de la généricité. Effectivement, std::string_view n'est pas une classe polymorphique, qu'on va pouvoir utiliser partout et qui va tout gérer en interne de façon transparente. Mais ça, c'est de la conception à la Java, cela a un coût.
std::string_view est au contraire extrêmement générique, puisque cela permet créer des views sur n'importe quel type de chaînes (char*, std::string, QString, etc), voire sur des types qui ne sont pas des chaînes a la base (simplement en fournissant les spé template adéquates).
Je crois que tu devrais prendre un peu de recul. Si des centaines de devs expert en C++ et des comités de normalisation on fait un choix, il y a des raisons derrière.
> Mais, j'ai l'impression que ça change rien car il n'y a pas de constructeur par déplacement pour un String Literal dans std::string?
Il y a les versions avec char* qui ne font pas d'objet intermédiaire. Mais comme dit @gbdivers, un constructeur de déplacement prend le même type que la classe.
> dans le cas du COW, il n'y a pas besoin de copier la chaine, tant qu'elle est utilisé en lecture.
C'est sûr ce point qu'il y a un problème. Pour garantir la lecture seule, il ne faut utiliser que des fonctions constantes. Or, la plupart des fonctions membres de std::string on 2 versions et manipuler une chaîne non constante est très facile, même dans le cas d'une lecture seule.
std::string s;
std::getline(std::cin, s);
for (char c : s) { // oups, std::string::begin/end doit faire une copie (itérateur non const)
}
if (s[0]) { // oups, operator[] retourne un char& => ce n'est pas une lecture seule
}
Il n'y aura pas toujours la copie, mais le COW oblige tout de même à vérifier que le compteur de référence soit à 1. Condition très coûteuse dans un parcours. D'ailleurs, Qt recommande l'usage de qconst ou autres mécanisme similaire pour éliminer ce genre de test.
Autres problèmes du COW: le multithreading. Et comme je disais plus haut, des problèmes avec les itérateurs comme ce fut le cas avec libstdc++ dans certaines conditions.
std::string s1 = "plop";
std::string s2 = s1;
auto it = std::as_const(s2).begin();
s2[0] = 'A';
// *it != s2[0]
Actuellement, les implémentations font du SSO (Short String Optimization). C'est aussi le cas de std::function. Je crois que les implémentations de libc++ et libstdc++ permettent d'avoir 8 caractères avant de faire une allocation dynamique et l'implémentation de facebook (folly::string) jusqu'à 15.
Le COW est utile dans certaines circonstances, mais il pose aussi pas mal de problème. Je trouve que le seul moment où il passe bien, c'est avec des objets qui fournissent une unique fonction pour accéder aux données non-const. Même s'il y a toujours le problème des références invalides comme pour l'exemple au-dessus.
std::string s1 = "plop";
std::string s2 = s1;
auto it = std::as_const(s2).begin();
s2[0] = 'A';
// *it != s2[0]
Même s'il y a toujours le problème des références invalides comme pour l'exemple au-dessus.
Est-ce que c'est un problème ou c'est le résultat attendu? Ta référence est valide car tu ne veux surtout pas modifier une constante. C'est exactement le but recherché du COW.
Je vois 2 scénarios possibles: #1 Tu considère que modifier l'élément même qui se fait itérer est une mauvaise pratique, dans ce cas le COW crée une copie de l'objet en cas de modification, donc c'est bien le résultat voulu comme le démontre ton exemple et c'est encore plus safe.
#2 Tu autorise la modification, tu fais un itérateur custom qui utilise l'index au lieu d'un pointeur et le tour est joué.
jo_link_noir a écrit:
Autres problèmes du COW: le multithreading.
En quoi? J'aurais pensé le contraire. Tu peux facilement avoir une ressource string partagé entre plusieurs threads. Si un de ceux-ci la modifier. Il en créer automatiquement une copie, il en devient en quelque sorte propriétaire. Il faut seulement avoir la variable du compte par référence en atomic.
jo_link_noir a écrit:
C'est sûr ce point qu'il y a un problème. Pour garantir la lecture seule, il ne faut utiliser que des fonctions constantes. Or, la plupart des fonctions membres de std::string on 2 versions et manipuler
Tu suggère de faire une surcouche à std::string, qui ne serait sans doute pas optimal je te l'accorde. Mais je parle plutôt du principe lui même, après tu l'implémente comme tu veux.
GZE, un moteur multiplateforme, adapté pour de la 2D, 3D et création de logiciels.
En quoi? J'aurais pensé le contraire. Tu peux facilement avoir une ressource string partagé entre plusieurs threads. Si un de ceux-ci la modifier. Il en créer automatiquement une copie, il en devient en quelque sorte propriétaire. Il faut seulement avoir la variable du compte par référence en atomic.
Payer des atomic à chaque modification d'une string ?!
Oui, je reste un peu mitiger là dessus, je préfère séparer complètement les données entre thread. Mais sinon ça reste pas pire qu'un shared_ptr qui utilise les atomic à fond.
En théorie ça reste le même coût qu'une variable normal, mais je soupçonne que le compilo ne peut appliquer certaines optimisations?
GZE, un moteur multiplateforme, adapté pour de la 2D, 3D et création de logiciels.
Oui, je reste un peu mitiger là dessus, je préfère séparer complètement les données entre thread. Mais sinon ça reste pas pire qu'un shared_ptr qui utilise les atomic à fond.
Ben façon de dire hein. std::shared_ptr, tu paies un accès atomique seulement quand tu copies le pointeur, ce qui est censé être rare. Là :
for(char& c : my_string){
c = '0' ;
}
Tu fais littéralement my_string.size() opération atomiques.
Maeiky a écrit:
(1) En théorie ça reste le même coût qu'une variable normal, (2) mais je soupçonne que le compilo ne peut appliquer certaines optimisations?
(1) Non, selon l'opération le coût varie. Au minimum, à supposer que les opérations en mode read/acquire soient suffisantes (je ne suis pas convaincu du tout, il faudrait faire une preuve), sur x86 c'est le même coût, en ARM c'est plus cher (largement). Si tu as besoin de consistance séquentielle (ce qui me semble plus probable), là même sur x86, tu paies un mfence qui flush des caches dans tous les sens et verrouille des choses pendant l'opération.
(2) Plein, en fait (see: "Common Compiler Optimization are Invalid under C11 Memory Model").
1) je constate que lorsque l'on passe un literal à setName, en tant que fonction template, il n'y a pas création d'objet temporaire au moment du passage de l'argument dans la fonction template.
Par contre, il semble qu'il y ait création d'un objet temporaire par l'opérateur d'affectation par déplacement , ce qui peut se comprendre étant donné que ce n'est pas une fonction template.
2) Par contre , si je fais :
void setName(T&& newName)
{
name = std::move(newName);
}
il y a création d'un objet temporaire au tout début.
Par contre, l'assignation par déplacement ne créé pas d'objet temporaire. Pourquoi pas dans ce cas ?
Donc comment se fait-il que dans le cas de la fonction template
template<typename T>
void setName(T&& newName)
{
name = std::forward<T>(newName);
}
, il y a création d'un objet temporaire précisément au moment de l'affectation par déplacement (je veux dire au moment du passage de l'argument à l'opérateur d'affectation par déplacement) alors que ça n'est pas vrai dans le cas de la surcharge lorsque l'on fait un passage par une Rvalue Reference ?
*****
Mon hypothèse qui me semble la plus plausible, c'est :
l'opérateur d'affectation par déplacement est défini en paramètre avec une référence rvalue, donc dans le cas d'une fonction template, le literal string étant transmis tel quel à l'opérateur d'affectation par déplacement, il est donc converti à ce moment-là seulement.
Par contre dans le cas de la surcharge avec une référence rvalue, le temporaire est déjà créé, donc c'est un string Rlvalue (utilisation de std::move) qui est transmis à l'opérateur d'affectation par déplacement. Donc pas besoin d'en recréer un dans ce cas
J'aimerais avoir ton avis sur ce qu'il y a derrière std::string:
// _Rep: string representation
// Invariants:
// 1. String really contains _M_length + 1 characters: due to 21.3.4
// must be kept null-terminated.
// 2. _M_capacity >= _M_length
// Allocated memory is always (_M_capacity + 1) * sizeof(_CharT).
// 3. _M_refcount has three states:
// -1: leaked, one reference, no ref-copies allowed, non-const.
// 0: one reference, non-const.
// n>0: n + 1 references, operations require a lock, const.
// 4. All fields==0 is an empty string, given the extra storage
// beyond-the-end for a null terminator; thus, the shared
// empty string representation needs no constructor.
struct _Rep_base
{
size_type _M_length;
size_type _M_capacity;
_Atomic_word _M_refcount;
};
/**
* @brief Subscript access to the data contained in the %string.
* @param __pos The index of the character to access.
* @return Read/write reference to the character.
*
* This operator allows for easy, array-style, data access.
* Note that data access with this operator is unchecked and
* out_of_range lookups are not defined. (For checked lookups
* see at().) Unshares the string.
*/
reference
operator[](size_type __pos)
{
// Allow pos == size() both in C++98 mode, as v3 extension,
// and in C++11 mode.
_GLIBCXX_DEBUG_ASSERT(__pos <= size());
// In pedantic mode be strict in C++98 mode.
_GLIBCXX_DEBUG_PEDASSERT(__cplusplus >= 201103L || __pos < size());
_M_leak();
return _M_data()[__pos];
}
void
_M_leak() // for use in begin() & non-const op[]
{
if (!_M_rep()->_M_is_leaked())
_M_leak_hard();
}
Le commentaire parle de l'assertion qui n'a aucun putain de rapport avec le COW, qui lui est interdit en C++11 comme la mentionné jo_link_noir il y a déjà un bail ...
> #1 Tu considère que modifier l'élément même qui se fait itérer est une mauvaise pratique, dans ce cas le COW crée une copie de l'objet en cas de modification, donc c'est bien le résultat voulu comme le démontre ton exemple et c'est encore plus safe.
Juste comme ça, le const_iterator n'empêche pas la modification de la chaîne. Dans mon exemple, il faut comprendre que l'itérateur de s2 pointe sur les données de s1. Donc modifier s1[0] change la valeur de l'itérateur. On a vu mieux pour le côté safe.
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
Discord NaN. Mon site.
Discord NaN. Mon site.
GZE, un moteur multiplateforme, adapté pour de la 2D, 3D et création de logiciels.
GZE, un moteur multiplateforme, adapté pour de la 2D, 3D et création de logiciels.
Discord NaN. Mon site.
GZE, un moteur multiplateforme, adapté pour de la 2D, 3D et création de logiciels.
GZE, un moteur multiplateforme, adapté pour de la 2D, 3D et création de logiciels.
GZE, un moteur multiplateforme, adapté pour de la 2D, 3D et création de logiciels.
Discord NaN. Mon site.
GZE, un moteur multiplateforme, adapté pour de la 2D, 3D et création de logiciels.
GZE, un moteur multiplateforme, adapté pour de la 2D, 3D et création de logiciels.
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C
GZE, un moteur multiplateforme, adapté pour de la 2D, 3D et création de logiciels.
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C
Discord NaN. Mon site.
GZE, un moteur multiplateforme, adapté pour de la 2D, 3D et création de logiciels.
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C
Discord NaN. Mon site.
GZE, un moteur multiplateforme, adapté pour de la 2D, 3D et création de logiciels.
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C
GZE, un moteur multiplateforme, adapté pour de la 2D, 3D et création de logiciels.