Questions diverses sur un projet

a marqué ce sujet comme résolu.

Bonsoir,

J'ouvre un nouveau topic pour vous posez quelques questions sur le C++ et la modélisation d'un projet. Mon projet est déjà bien avancé bien que je rencontre encore des difficultés. Ayant un certains laps de temps encore, j'aimerais pouvoir améliorer considérablement ce que j'ai fait. Ce topic fait suite à un topic que j'avais ouvert sur progdupeu.pl où Freedom m'avait très gentillement répondu : Modéliser les TLV.

Ayant peu d'expérience en programmation, j'aimerais améliorer ma technique aussi bien en C++ que de façon général pour avoir des programmes un peu plus safe.

Mon objectif premier est d'utiliser un maximum des idiomes du C++ afin d'éviter les écueils du C tout en restant à mon avis. Je souhaitais aussi utiliser un maximum les fonctionnalités de C++ 11 (voir 14) afin d'avoir un code moderne.

Si je vous dis tout ça, c'est notamment pour vous guider dans les réponses à mes futures questions :p

Présentation générale

Le but du projet est d'implémenter un protocole réseaux décentralisée qui permet l'échange de données. L'objectif étant que tous le monde reçoit les données de tous le monde. Le protocole s'articule autour de paquet qui sont composés de Tlv. Un Tlv encapsule de l'information à communiquer à un pair. Pour plus d'informations sur le projet, je peux vous l'envoyer en privé.

Question 1

Ma première question concerne les types. Ayant goûté aux joies de la programmation fonctionnelle avec Ocaml ou Haskell, avoir un typage fort est extrêmement intéressant. J'ai donc essayé d'utiliser au mieux les types fournit par la librairie standard. Cependant, durant l'implémentation du projet, je me suis rendu compte que j'avais de nombreux cast à faire à cause des données qui transitaient à travers le réseaux.

Par exemple, si je souhaitais stocker une donnée sur un octet, j'utilisais le type uint8_t que je devrais caster plus tard en unsigned char. Ou bien un uint64_t que je devrais caster ensuite en unsigned char[8] .

Donc après coup, je me demande si les types uint8_t sont vraiment utile. Mon idée, c'était quand on voit ça, on se dit très bien ça va être un entier sur un octet. Alors qu'un unsigned char c'est peut-être moins évident ?

D'autre part, je manipule aussi des données binaires de longueur variable. En me renseignant, j'utilisais un basic_string<unsigned char> , est-ce le bon type à utiliser ?

Question 2

L'échange de données avec d'autres pairs ne se fait pas forcément de façon synchrone. En particulier, je souhaiterais pouvoir envoyer des données quand un timeout se déclenche. Comment faire ça avec la librairie boost::asio ? Chacune de mes tentatives ont été des échecs.

J'avais un serveur qui écoutait sur un port x. Un client (qui avec une autre socket) envoyait ses données sur le même port (à une adresse spécifique). Cependant lorsque le client se mettait en route, le serveur ne recevait plus aucun paquet. Je me demandais donc si il n'y avait pas une façon plus simple d'envisager la chose.

Question 3

Je dois gérer une interface utilisateur que je compte encapsuler dans une classe. L'interface devra être exécutée dans un thread particulier. Afin de faire ça, est-ce recommandable d'utiliser une méthode run qui sera implémentée par la classe UI ? Ou bien il existe une façon plus idiomatique de le faire ?

Question 4

Le programme peut choisir la façon dont sera stocké les données récupérées grâce à une option sur la ligne de commande. Les options sont gérés avec boost_program_options. Lorsque ce choix se fait sur une certaine BDD, comme ce choix étant connu au début de l'éxécution, je me demandais s'il était pas préférable que le module qui gère les options renvoie directement un pointeur sur la BDD plutôt que ce soit l'utilisateur de la classe qui le fasse lui-même. Est-ce pertinent ? Ou bien ça n'a aucune importance ?

Question 5

Je souhaite sécurisé mes données en donnant comme identifiant à mes données un hash. Je pensais donc utiliser MD5 ou SHA-1. J'ai été surpris de voir que Boost ne proposait pas ces deux méthodes. Le recours à CryptoC++ est le meilleur moyens de faire ça ?

Le second problème vient du fait que l'identifiant doit faire 8 bytes, alors que les hash sont plus élevés. J'applique donc une fonction sur les bytes obtenus pour en avoir que 8. Est-ce finalement une bonne méthode d'utiliser MD5 ou SHA-1 , ou bien il existe une meilleure solution ?

Question 6

J'a regardé rapidement, mais il existe une framework pour mettre en place facilement des tests unitaires ? Il y a CUTE, mais j'ai pas encore pris le temps de regarder si c'était vraiment rapide à mettre en place.

Je crois que c'est à peu près tout niveau question. Si vous voulez un accès au code source je pourrais le mettre en ligne sur git ce soir. Il est partiellement commenté (pour les .h) en utilisant doxygen.

Merci d'avance pour tous les commentaires que vous pourrez me suggérer,

Bonne soirée ;)

Bonsoir

J'ouvre un nouveau topic pour vous posez quelques questions sur le C++ et la modélisation d'un projet. Mon projet est déjà bien avancé bien que je rencontre encore des difficultés. Ayant un certains laps de temps encore, j'aimerais pouvoir améliorer considérablement ce que j'ai fait. Ce topic fait suite à un topic que j'avais ouvert sur progdupeu.pl où Freedom m'avait très gentillement répondu : Modéliser les TLV.

Ayant peu d'expérience en programmation, j'aimerais améliorer ma technique aussi bien en C++ que de façon général pour avoir des programmes un peu plus safe.

Mon objectif premier est d'utiliser un maximum des idiomes du C++ afin d'éviter les écueils du C tout en restant à mon avis. Je souhaitais aussi utiliser un maximum les fonctionnalités de C++ 11 (voir 14) afin d'avoir un code moderne.

Si je vous dis tout ça, c'est notamment pour vous guider dans les réponses à mes futures questions :p

Quelques mots clés à google : RAII, single responsibility principle, Almost Always Auto, Liskov substitution principle

Quelques liens du moment :

https://isocpp.org/blog/2014/12/myths-1

https://isocpp.org/blog/2014/12/myths-2

https://isocpp.org/blog/2014/12/myths-3

http://maintainablecode.logdown.com/posts/247159-a-corollary-on-overloading

http://maintainablecode.logdown.com/posts/247153-type-dependent-code-reuse-part-1-the-basics

Pour les gens têtes brulées qui viendraient du Haskell :

http://cpptruths.blogspot.fr/2014/08/fun-with-lambdas-c14-style-part-3.html

https://www.youtube.com/watch?v=L2SktfaJPuU

https://github.com/ldionne/hana

Question 1

Ma première question concerne les types. Ayant goûté aux joies de la programmation fonctionnelle avec Ocaml ou Haskell, avoir un typage fort est extrêmement intéressant. J'ai donc essayé d'utiliser au mieux les types fournit par la librairie standard. Cependant, durant l'implémentation du projet, je me suis rendu compte que j'avais de nombreux cast à faire à cause des données qui transitaient à travers le réseaux.

Par exemple, si je souhaitais stocker une donnée sur un octet, j'utilisais le type uint8_t que je devrais caster plus tard en unsigned char. Ou bien un uint64_t que je devrais caster ensuite en unsigned char[8] .

Donc après coup, je me demande si les types uint8_t sont vraiment utile. Mon idée, c'était quand on voit ça, on se dit très bien ça va être un entier sur un octet. Alors qu'un unsigned char c'est peut-être moins évident ?

Attention la plupart des types standards n'ont pas un typage "fort". Un uint8 n'est rien d'autre qu'un unsigned char pour le compilateur il ne s'agit que d'un alias. Aucun cast n'est nécessaire entre deux alias (typedefs) du même type. Pour se rapprocher le plus possible d'un typage "fort" on a la possibilité de créer artificiellement des strong-typedef. Ceci dit, la philosophie du C++ c'est aussi de chercher à exprimer le plus clairement possible ce que l'on fait au travers d'un choix judicieux des noms de variables et des noms des types utilisés. De ce point de vue, je préfèrerai toujours utiliser uint8_t pour manipuler un buffer plutôt qu'un unsigned char que j'utiliserai par exemple plutôt pour manipuler des caractères utf-8.

D'autre part, je manipule aussi des données binaires de longueur variable. En me renseignant, j'utilisais un basic_string<unsigned char> , est-ce le bon type à utiliser ?

Je serai curieux de connaitre les raisons de cette recommandation. Là comme ca je partirai plutôt pour un std::vector<uint8_t> ou encore un std::unique_ptr<uint8_t[]> qui selon moi donneraient plus de sens pour un buffer de données binaires.

Question 2

L'échange de données avec d'autres pairs ne se fait pas forcément de façon synchrone. En particulier, je souhaiterais pouvoir envoyer des données quand un timeout se déclenche. Comment faire ça avec la librairie boost::asio ? Chacune de mes tentatives ont été des échecs.

Je n'ai personnellement jamais utilisé boost::asio mais je suis cependant familier de sa documentation. Résoudre ce problème avec boost::asio ne devrait pas être trop compliqué. A l'occasion je me pencherai dessus en début d'année. Ca me fera un bon exercice. Le seul truc vraiment alien dans boost::asio ce sont les coroutines :D.

Question 3

Je dois gérer une interface utilisateur que je compte encapsuler dans une classe. L'interface devra être exécutée dans un thread particulier. Afin de faire ça, est-ce recommandable d'utiliser une méthode run qui sera implémentée par la classe UI ? Ou bien il existe une façon plus idiomatique de le faire ?

Si l'on utilise une bibliothèque (framework? Qt?) pour créer une UI, généralement on a pas besoin de se préoccuper des thread pour rendre son UI non blocante. Par contre si l'on a des traitements longs à faire, il est préférable de les effectuer dans un thread séparé. Quoi qu'il en soit je ne créerai pas de fonction membre run dans la classe UI.

Question 4

Le programme peut choisir la façon dont sera stocké les données récupérées grâce à une option sur la ligne de commande. Les options sont gérés avec boost_program_options. Lorsque ce choix se fait sur une certaine BDD, comme ce choix étant connu au début de l'éxécution, je me demandais s'il était pas préférable que le module qui gère les options renvoie directement un pointeur sur la BDD plutôt que ce soit l'utilisateur de la classe qui le fasse lui-même. Est-ce pertinent ? Ou bien ça n'a aucune importance ?

Cela a de l'importance, renvoyer un pointeur vers la BDD crée une dépendance supplémentaire inutile entre le module qui interprète la ligne de commande et la bibliothèque utilisée pour la BDD. Si un jour on remplace le système de BDD par un autre type de BDD ou par des fichiers XML, il faut tout revoir…

Question 5

Je souhaite sécurisé mes données en donnant comme identifiant à mes données un hash. […]

Pas d'avis sur le sujet.

Question 6

J'a regardé rapidement, mais il existe une framework pour mettre en place facilement des tests unitaires ? Il y a CUTE, mais j'ai pas encore pris le temps de regarder si c'était vraiment rapide à mettre en place.

J'aime bien Boost.Test mais ce n'est pas vraiment "rapide" à mettre en place… Je n'ai pas vraiment de recommandation sur le sujet à part ça.

+0 -0

Bonsoir

J'ouvre un nouveau topic pour vous posez quelques questions sur le C++ et la modélisation d'un projet. Mon projet est déjà bien avancé bien que je rencontre encore des difficultés. Ayant un certains laps de temps encore, j'aimerais pouvoir améliorer considérablement ce que j'ai fait. Ce topic fait suite à un topic que j'avais ouvert sur progdupeu.pl où Freedom m'avait très gentillement répondu : Modéliser les TLV.

Ayant peu d'expérience en programmation, j'aimerais améliorer ma technique aussi bien en C++ que de façon général pour avoir des programmes un peu plus safe.

Mon objectif premier est d'utiliser un maximum des idiomes du C++ afin d'éviter les écueils du C tout en restant à mon avis. Je souhaitais aussi utiliser un maximum les fonctionnalités de C++ 11 (voir 14) afin d'avoir un code moderne.

Si je vous dis tout ça, c'est notamment pour vous guider dans les réponses à mes futures questions :p

Quelques mots clés à google : RAII, single responsibility principle, Almost Always Auto, Liskov substitution principle

Quelques liens du moment :

https://isocpp.org/blog/2014/12/myths-1

https://isocpp.org/blog/2014/12/myths-2

https://isocpp.org/blog/2014/12/myths-3

http://maintainablecode.logdown.com/posts/247159-a-corollary-on-overloading

http://maintainablecode.logdown.com/posts/247153-type-dependent-code-reuse-part-1-the-basics

Pour les gens têtes brulées qui viendraient du Haskell :

http://cpptruths.blogspot.fr/2014/08/fun-with-lambdas-c14-style-part-3.html

https://www.youtube.com/watch?v=L2SktfaJPuU

https://github.com/ldionne/hana

Merci pour tous ces liens, je vais prendre un peu de temps pour les regarder.

Question 1

Ma première question concerne les types. Ayant goûté aux joies de la programmation fonctionnelle avec Ocaml ou Haskell, avoir un typage fort est extrêmement intéressant. J'ai donc essayé d'utiliser au mieux les types fournit par la librairie standard. Cependant, durant l'implémentation du projet, je me suis rendu compte que j'avais de nombreux cast à faire à cause des données qui transitaient à travers le réseaux.

Par exemple, si je souhaitais stocker une donnée sur un octet, j'utilisais le type uint8_t que je devrais caster plus tard en unsigned char. Ou bien un uint64_t que je devrais caster ensuite en unsigned char[8] .

Donc après coup, je me demande si les types uint8_t sont vraiment utile. Mon idée, c'était quand on voit ça, on se dit très bien ça va être un entier sur un octet. Alors qu'un unsigned char c'est peut-être moins évident ?

Attention la plupart des types standards n'ont pas un typage "fort". Un uint8 n'est rien d'autre qu'un unsigned char pour le compilateur il ne s'agit que d'un alias. Aucun cast n'est nécessaire entre deux alias (typedefs) du même type. Pour se rapprocher le plus possible d'un typage "fort" on a la possibilité de créer artificiellement des strong-typedef. Ceci dit, la philosophie du C++ c'est aussi de chercher à exprimer le plus clairement possible ce que l'on fait au travers d'un choix judicieux des noms de variables et des noms des types utilisés. De ce point de vue, je préfèrerai toujours utiliser uint8_t pour manipuler un buffer plutôt qu'un unsigned char que j'utiliserai par exemple plutôt pour manipuler des caractères utf-8.

Cette réponse me parait curieuse. Il me semble mais je n'ai pas vérifié que le compilateur me fait un warning si je ne fais pas explicitement un static_cast.

D'autre part, je manipule aussi des données binaires de longueur variable. En me renseignant, j'utilisais un basic_string<unsigned char> , est-ce le bon type à utiliser ?

Je serai curieux de connaitre les raisons de cette recommandation. Là comme ca je partirai plutôt pour un std::vector<uint8_t> ou encore un std::unique_ptr<uint8_t> qui selon moi donneraient plus de sens pour un buffer de données binaires.

Je ne retrouve pas le lien, mais j'avais vu un post sur SO où la personne ne le justifiait pas mais ça semblait être un bon choix pour lui. Pourquoi un std::unique_ptr et pas un shared_ptr ? On peut souhaiter avoir plusieurs personnes qui modifient le buffer non ?

Question 2

L'échange de données avec d'autres pairs ne se fait pas forcément de façon synchrone. En particulier, je souhaiterais pouvoir envoyer des données quand un timeout se déclenche. Comment faire ça avec la librairie boost::asio ? Chacune de mes tentatives ont été des échecs.

Je n'ai personnellement jamais utilisé boost::asio mais je suis cependant familier de sa documentation. Résoudre ce problème avec boost::asio ne devrait pas être trop compliqué. A l'occasion je me pencherai dessus en début d'année. Ca me fera un bon exercice. Le seul truc vraiment alien dans boost::asio ce sont les coroutines :D.

Je vais essayer de faire un snippet de ce que je veux d'ici la nouvelle année. J'en ai déjà fait un mais je crois qu'il ne fonctionne pas très bien encore. Et pour les coroutines je ne connais pas du tout.

Question 3

Je dois gérer une interface utilisateur que je compte encapsuler dans une classe. L'interface devra être exécutée dans un thread particulier. Afin de faire ça, est-ce recommandable d'utiliser une méthode run qui sera implémentée par la classe UI ? Ou bien il existe une façon plus idiomatique de le faire ?

Si l'on utilise une bibliothèque (framework? Qt?) pour créer une UI, généralement on a pas besoin de se préoccuper des thread pour rendre son UI non blocante. Par contre si l'on a des traitements longs à faire, il est préférable de les effectuer dans un thread séparé. Quoi qu'il en soit je ne créerai pas de fonction membre run dans la classe UI.

J'aurais dû préciser que l'interface utilisateur allait seulement être console. Elle est vraiment basique. J'ai pas envie d'avoir un gros framework qui gère ça.

Question 4

Le programme peut choisir la façon dont sera stocké les données récupérées grâce à une option sur la ligne de commande. Les options sont gérés avec boost_program_options. Lorsque ce choix se fait sur une certaine BDD, comme ce choix étant connu au début de l'éxécution, je me demandais s'il était pas préférable que le module qui gère les options renvoie directement un pointeur sur la BDD plutôt que ce soit l'utilisateur de la classe qui le fasse lui-même. Est-ce pertinent ? Ou bien ça n'a aucune importance ?

Cela a de l'importance, renvoyer un pointeur vers la BDD crée une dépendance supplémentaire inutile entre le module qui interprète la ligne de commande et la bibliothèque utilisée pour la BDD. Si un jour on remplace le système de BDD par un autre type de BDD ou par des fichiers XML, il faut tout revoir…

Bon point. Je change ça tout de suite.

Question 5

Je souhaite sécurisé mes données en donnant comme identifiant à mes données un hash. […]

Pas d'avis sur le sujet.

Question 6

J'a regardé rapidement, mais il existe une framework pour mettre en place facilement des tests unitaires ? Il y a CUTE, mais j'ai pas encore pris le temps de regarder si c'était vraiment rapide à mettre en place.

J'aime bien Boost.Test mais ce n'est pas vraiment "rapide" à mettre en place… Je n'ai pas vraiment de recommandation sur le sujet à part ça.

iNaKoll

Je vais aller voir un peu Boost:Test tout de même.

Merci beaucoup d'avoir pris le temps de me répondre.

Bonne soirée ;)

Bonjour

Cette réponse me parait curieuse. Il me semble mais je n'ai pas vérifié que le compilateur me fait un warning si je ne fais pas explicitement un static_cast.

Non, je reste catégorique : pas de warning et cast inutile entre deux alias du même type.

Pourquoi un std::unique_ptr et pas un shared_ptr ? On peut souhaiter avoir plusieurs personnes qui modifient le buffer non ?

La bonne question ce serait, "Pourquoi utiliser shared_ptr quand on peut utiliser unique_ptr?". Le shared_ptr est bien pratique quand on se retrouve dans des situations un peu compliqué et qu'aucune classe n'est réellement responsable d'une zone mémoire particulière. Par contre dés que l'on sait à l'avance associer la durer de vie d'un objet à un autre objet ou à un scope, il est grandement préférable d'utiliser unique_ptr qui a un coup très faible (nul comparé au shared_ptr).

Si l'on veut que plusieurs personnes modifient le même buffer, on peut passer à ces personnes une référence sur le unique_ptr ou directement passer un pointeur nu (méthode unique_ptr::get()). En C++ moderne, quand on passe un pointeur nu à une fonction cela sous-entend que la fonction n'est pas responsable désallocation de la mémoire.

Question 6

J'ai regardé rapidement, mais il existe une framework pour mettre en place facilement des tests unitaires ? Il y a CUTE, mais j'ai pas encore pris le temps de regarder si c'était vraiment rapide à mettre en place.

Petit retour d'expérience là dessus : J'ai tenté Boost.Test, et j'ai abandonné: j'avais des difficultés à intégrer ça à CMake. Du coup je suis passé à Googletest (ce truc est utilisé par des gros projets, donc ça en jette !), et ça se passait plutôt bien, c'était sympa. Le seul problème est que c'est une bibliothèque, et donc qu'il faut l'installer avant utilisation. Et récemment, j'ai trouvé Catch, et c'est plutôt un super framework ! Rien qu'un .hpp, avec un temps de compilation pas trop grand, des tests qui s'écrivent avec une syntaxe C++ claire et qui exécutent dans des contextes séparés. J'utilise celui là maintenant.

+0 -0

Remarque sur unique_ptr/shared_ptr. Le premier ne permet pas de garantir la durée des vies des ressources dans un contexte multithread (Cf Références, unique_ptr, shared_ptr et weak_ptr), le second oui. Et on semble être dans une utilisation multithread ici.

La règle "si pointeur nu, alors on gère pas la destruction" n'est pas assez répandue pour l'appliquer les yeux fermées. Perso, je pense qu'un code C++ "moderne" ne doit plus avoir de pointeur nu, donc si j'en trouve un, je considère que c'est du C++ old school et que je dois gérer manuellement le delete.

(dans le pire des cas, si on est forcé de manipuler un pointeur nu provenant d'un unique_ptr ou une référence sur un unique_ptr, ne jamais les passer à un autre thread)

HS : tu as lu le tuto sur le C++14 sur ce site ?

Comme tu parles d'UI ensuite (et donc potentiellement de Qt), tu peux aussi utiliser QtNetwork. Idem pour boost_program_options, tu peux utiliser QCommandLineParser de mémoire). Pour les tests, tu as QtTest.

+0 -0

Remarque sur unique_ptr/shared_ptr. Le premier ne permet pas de garantir la durée des vies des ressources dans un contexte multithread (Cf Références, unique_ptr, shared_ptr et weak_ptr), le second oui. Et on semble être dans une utilisation multithread ici.

Je ne suis pas tout à fait du même avis. Prendre systématiquement le raccourci multithread -> shared_ptr serait une erreur. On peut très bien avoir des unique_ptr dans un code multithread. Cependant, comme c'est toujours le cas avec un code multithread il ne faut pas faire n'importe quoi. Cette remarque vaut aussi pour l'utilisation de shared_ptr en MT. Pour utiliser correctement unique_ptr dans un code MT on peut par exemple très bien "déplacer" (move) le unique_ptr lors de la création d'un nouveau std::thread ou encore le retourner depuis un thread à l'aide de std::async ou de std::packaged_task.

La règle "si pointeur nu, alors on gère pas la destruction" n'est pas assez répandue pour l'appliquer les yeux fermées. Perso, je pense qu'un code C++ "moderne" ne doit plus avoir de pointeur nu, donc si j'en trouve un, je considère que c'est du C++ old school et que je dois gérer manuellement le delete.

C'est vrai, ce n'est pas très rependu… Maintenant il n'appartient qu'à nous de le répandre le C++ "moderne". Personnellement, je trouve que l'on comprend bien cette idée avec le code suivant :

1
2
3
4
struct Foo {
    Bar* bar_;
    Foo(Bar& bar) : bar_(&bar) {}
};

En français on pourrait traduire : "OK, bar_ est en fait une référence sous la forme d'un pointeur nu qui ne peut pas être nul. Pour auto-documenter mon code j'insiste sur le fait que bar_ est une référence externe à mes objets Foo."

(dans le pire des cas, si on est forcé de manipuler un pointeur nu provenant d'un unique_ptr ou une référence sur un unique_ptr, ne jamais les passer à un autre thread)

Là je ne suis pas du tout d'accord. Un pointeur nu peut très bien apparaitre dans un code propre et "moderne" (cf code ci-dessus). A mon humble avis, déplacer un unique_ptr dans un nouveau thread pourrait presque être un idiom d'utilisation. Tout ce qu'il y a de plus safe. Du point de vu du nouveau thread toutes les opérations mémoire du thread parent effectuées avant l'appel du constructeur de std::thread sont visibles dans le nouveau thread dés le début.

Effectivement, mon message pouvait laisser penser que je faisais le raccourci "multithread = shared_ptr". Je ne parlais que de la situation présente, Saroupille parle de plusieurs utilisateurs qui accèdent aux données (donc contexte multi-thread et ressources partagées).

Dans le cas général, il y a bien sur d'autres approches possibles que shared_ptr.

Pourquoi utiliser un pointeur dans ton exemple, plutôt qu'une référence ou un reference_wrapper ?

Dans tous les cas, je ne suis pas convaincu de la pertinence de garder des pointeurs nus en C++ "moderne" (or code volontairement bas niveau, mais qui sera alors correctement encapsulé). Mais il faudra plus de recul pour être correctement fixé sur ce point. (HS : je viens de recevoir "Effective Modern C++", je ne l'ai pas encore lu, je ne sais pas s'il aborde ce point. Par contre, je sais que Sutter continue de présenter les pointeurs nus dans ses confs sur le C++ "moderne". Je ne connais pas non plus la position de Stroustrup et des autres)

+0 -0

Pourquoi utiliser un pointeur dans ton exemple, plutôt qu'une référence ou un reference_wrapper ?

  1. Pour marquer le coup et pour varier les plaisirs! :-°
  2. La référence n'est pas réinitialisable ni copiable (!!)
  3. Le pointeur nu est plus simple à utiliser et plus commun que le reference_wrapper.
  4. Il arrive aussi parfois qu'à force d'utiliser des références dans un code on en vienne à oublier si tel ou tel lvalue appartient bien à l'objet courant. L'idée me semblait un peu étrange au départ mais au final je pense que c'est une bonne idée à utiliser à bon escient et de façon consistante. Ne pas utiliser en C-with-class et à éviter en interface avec un code client.
  5. En C++ moderne le pointeur nu n'est plus associé à la notion de propriétaire d'une zone mémoire donc tant qu'on ne fait pas de new/delete et que le code appelant gère la durée de vie pour nous c'est OK :)

Dans tous les cas, je ne suis pas convaincu de la pertinence de garder des pointeurs nus en C++ "moderne" (or code volontairement bas niveau, mais qui sera alors correctement encapsulé). Mais il faudra plus de recul pour être correctement fixé sur ce point. (HS : je viens de recevoir "Effective Modern C++", je ne l'ai pas encore lu, je ne sais pas s'il aborde ce point. Par contre, je sais que Sutter continue de présenter les pointeurs nus dans ses confs sur le C++ "moderne". Je ne connais pas non plus la position de Stroustrup et des autres)

gbdivers

Alexander Stepanov (inventeur de la STL) utilise les pointeurs nus dans ce sens (points 2. et 4. ci-dessus) dans ses cours "Efficient programming with components" :

  1. http://www.stepanovpapers.com/a9_2013/lectures.tar.gz
  2. https://www.youtube.com/playlist?list=PLHxtyCq_WDLXryyw91lahwdtpZsmo4BGD

Je crois pouvoir le dire sans me tromper mais Eric Niebler utilise des pointeurs nu de la sorte dans sa bibliothèque boost range-v3 : exemple ici ligne 137 d'un foncteur copiable.

Je ne rejette pas l’idée des pointeurs nus en C++ "moderne", je crois juste que leur utilisation va devenir très domaine spécifique (comme par exemple l’implémentation des ranges, que tu cites). En particulier, dans les cas d'utilisation que tu cites, je suis pas convaincu (mais je verrais avec le temps).

Intéressant ces vidéos de Stepanov, je ne les ai jamais vu. Faudra que je regarde ça

+0 -0

Ah c'est un article. Cependant si vous avez des références sur des futurs livres ou articles qui parlent de C++14 je suis preneur.

Sur la norme C++11 j'avais vu qu'un ou deux auteurs allaient pondre une nouvelle édition du livre. Mais vu l'ampleur des changements introduits par cette norme, une nouvelle édition ne suffit pas non ? Il n'y a pas des livres qui sont sortis basés complètement sur cette norme ?

Plusieurs auteurs ont mis à jour leurs livres pour le C++11, voire le C++14. Par exemple "The C++ Langage" de Stroustrup ou "C++ Primer" de Lippmann et Moo. Plus spécifique pour le C++14, Meyer vient de sortir "Effective Modern C++".

Une autre source sont les vidéos de CppCon sur YouTube : https://www.youtube.com/channel/UCMlGfpWw-RUdWX_JbLCukXg

Ah c'est un article.

Saroupille

:D on dirait presque que tu es déçu

+0 -0

@gbdivers : Sur les pointeurs nus, cette conf' de Herb Sutter pourrait t'intéresser (à partir de la minute 14).

Sinon, un gros point noir sur modern C++ concerne la différence entre unique_ptr et shared_ptr . Vous recommandez quoi comme article sur le sujet ?

Une chose que je n'ai pas compris c'est par exemple par défaut (entendre sans trop se poser de questions) j'utiliserais un shared_ptr hors vous avez l'air de dire que par défaut on utiliserait un unique_ptr. Ca m'échappe…

@Saroupille, pour les choix de pointeurs & cie -> http://exceptionsafecode.com/slides/svcc/2013/shared_ptr.pdf

En version simplifiée: en interface fonction:

  1. référence pour accès [in]
  2. unique_ptr pour les fonctions puit
  3. unique_ptr pour les fonction source (en sortie donc)
  4. optional pour les paramètres optionnels (on pourrait jouer avec les pointeurs nus aussi, c'est vrai…)

@Saroupille: si j'ai donné le lien vers les confs de CppCon, c'est que j'ai déjà vu les vidéos. Sutter est spécialiste de la prog concurrente, je n'ai pas de doute qu'il connait très bien les problématiques liées à l'utilisation des unique_ptr dans ce contexte.

Il est difficile d'avoir un consensus sur le pointeur intelligent "par défaut" (pour autant que cela ait un sens). Les shared sont plus safe en multi thread (la durée de vie, pas l'accès concurrent…), mais plus lourds que les unique (compteur interne inutile si tu as qu'un seul responsable). De plus, on a plus tendance à ne pas définir correctement qui est propriétaire et quelle est la durée de vie des objets managés avec les shared qu'avec les unique (phénomène de "tout le monde est propriétaire", qui mène à avoir aucun vrai propriétaire)

(HS : je crois que cette idée vient du fait que l'on présente souvent les weak comme solution aux références circulaires, alors qu'a mon sens, les weak doivent être utilisés aussi pour les observateurs sur une ressource. Donc Avec unique et shared, on ne doit avoir qu'un seul propriétaire par défaut)

Par défaut, je recommande le shared, pour le côté safe, mais avec qu'un seul propriétaire (donc qu'un seul shared sur une ressource, les autres utilisateurs de la ressources utilisent weak)

Cf mon article pour plus de détails

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte