Un meilleur job mieux payé ?

Deviens chef de projet, développeur, ingénieur, informaticien

Mets à jour ton profil pro

ça m'intéresse

GoingNative 2013 - Sean Parent - C++ Seasoning

Le , par germinolegrand, Membre expert
Sean Parent - C++ Seasoning
GoingNative 2013

La conférence de Sean Parent lors des GoingNative 2013 est disponible :


Sean Parent propose trois objectifs à viser lors de l'écriture de code C++ :

Pas de boucle brute

Une boucle brute est une boucle dans une fonction qui fait plus que l'algorithme de la boucle lui-même.

Ces boucles rendent la compréhension du code difficile, son analyse ardue, ce qui a pour conséquence de générer un code prompt à l'erreur, particulièrement dans les cas qui ne sont pas évidents, et dont les problèmes de performances sont difficiles à résoudre.

Quelles solutions ?

  • Utilisez un algorithme existant, standard de préférence s'il est disponible.
  • S'il n'est pas disponible, écrivez-le en tant que fonction générale.
  • Il serait bénéfique que chacun essaye de contribuer à une bibliothèque (de préférence open-source) en soumettant au moins un algorithme par an.
  • Si vous inventez un algorithme, écrivez un papier, faites des conférences, devenez célèbres ! Il n'y a rien de négatif à cela, bien que cela risque d'être extrêmement rare.


Un des plus grands problèmes est que les développeurs C++ ne connaissent pas leurs algorithmes : prenez du temps, apprenez-les, c'est vital ! Vous gagnerez du temps, des performances, de la lisibilité, de la sécurité.

Apprenez ce qu'ils font, apprenez à les reconnaître, apprenez à jouer avec en réduisant itérativement la complexité de votre code.

Le code généré sera également considérablement plus évolutif.

Pas de primitives de synchronisation brutes

Les primitives de synchronisation en question sont des mutex, des atomic, des sémaphores, des barrières.

Vous avez un pourcentage de chance extrêmement élevé de vous tromper, générant des erreurs du genre le plus teigneux : les data race ponctuelles (une ou deux fois par semaine, votre application va crasher, amusez-vous à débuguer ).

De plus, si vous voulez que vos performances augmentent le plus proportionnellement possible au nombre de cœurs, la synchronisation n'est pas votre alliée.

Il faut penser en termes de tâches asynchrones. Le standard ne fournit malheureusement aucun outil pour ceci actuellement.

S. Parent donne quelques clés pour disposer d'outils de base.

Pas de pointeurs bruts

Un pointeur brut peut être ici :
  • T* p = new T
  • std::unique_ptr<T>
  • std::shared_ptr<T>


On utilise les pointeurs pour un nombre conséquent de raisons. Très peu se justifient, notamment dans les interfaces. On doit être capable de manipuler des Objets, sans passer par des pointeurs bruts.

Pour les conteneurs, on dispose déjà de conteneurs non-intrusifs dans la STL, il n'est ainsi pas nécessaire d'hériter d'une classe Node pour être stocké dans un std::vector.

Le problème se pose pour le polymorphisme dynamique où l'on commence à vouloir ajouter une classe de base pour la hiérarchie afin de pouvoir la stocker, alors qu'il est possible de le faire de façon non-intrusive, ainsi que Sean Parent va le montrer dans ce code :
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class object_t { 
  public: 
    template<typename T> // T models Drawable 
    object_t(T x) : self_(make_shared<model<T>>(move(x))) 
    { } 
    void draw(ostream& out, size_t position) const 
    { self_->draw_(out, position); } 
     
  private: 
    struct concept_t { 
        virtual ~concept_t() = default; 
        virtual void draw() const = 0; 
    }; 
     
    template <typename T> 
    struct model : concept_t { 
        model(T x): data_(move(x)) { } 
        void draw(ostream& out, size_t position) const 
        { data_.draw(out, position); } 
         
        T data_; 
    }; 
   
    shared_ptr<const concept_t> self_; // [Ndlr] une copie de shared_ptr<const> copie l'objet contenu 
}; 
 
using document_t = vector<object_t>; 
 
void draw(const document_t& x, ostream& out, size_t position) 
{ 
    out << string(position, ' ') << "<document>" << endl; 
    for (const auto& e : x) e.draw(out, position + 2); 
    out << string(position, ' ') << "</document>" << endl; 
}
Les shared_ptr ne valent pas mieux que des globales : il devient impossible de résonner de façon locale sur le code, deux morceaux de code indépendants travaillent sur la même portion de mémoire.

Il faut garder la possibilité de raisonner localement : le code sera plus simple à analyser, plus facile à intégrer, plus généraliste, plus correct, plus efficace.

Sean Parent donne plus d'informations sur cette approche dans sa deuxième conférence intitulée Inheritance is the base class of Evil, "L'héritage est la classe mère du Vice" (résumé à venir prochainement).

Conférence précédente : Bjarne Stroustrup - L'Essence du C++
Évènement : GoingNative 2013

Et vous,
Qu'en pensez-vous ?


Vous avez aimé cette actualité ? Alors partagez-la avec vos amis en cliquant sur les boutons ci-dessous :


 Poster une réponse

Avatar de Luc Hermitte Luc Hermitte - Expert éminent https://www.developpez.com
le 10/09/2013 à 11:11
À noter aussi l'exemple didactique d'un refactoring de code dans le sujet "pas de raw for". Si on prend une liste d'éléments contigus (des random access containers), deux questions de réorganisation des éléments :
1- comment remonter (/descendre) un ensemble contigus d'éléments dans la liste ? -> avec std::rotate
2- comment prendre un ensemble non contigus d'éléments, et les déplacer ailleurs et les rendre contigus ? -> avec std::stable_partition.

D'où le "know your algorithms".
C'est une des meilleures illustrations du conseil, "utiliser les algorithmes standards" que j'ai pu voir. Souvent, on se cantonne à des for-each, des find_if faits à la main (à cause du besoin de l'écriture lourde des foncteurs), au mieux on utilise des std::sort. Là, on voit une mise en oeuvre d'un algo pas si trivial, et d'une façon efficace qui aura des impacts bénéfiques sur les performances.
Avatar de CedricMocquillon CedricMocquillon - Membre averti https://www.developpez.com
le 13/09/2013 à 16:59
Citation Envoyé par germinolegrand  Voir le message
Pas de pointeurs bruts

Un pointeur brut peut être ici :
  • T* p = new T
  • std::unique_ptr<T>
  • std::shared_ptr<T>

J'ai personnellement du mal à voir comment ça peut être compatible si on a des objets à sémantique d'entité qui sont référencés par d'autres (type association UML) : il faut bien un lien entre la personne et ses magasines (on aura un truc comme un vector<Magazine*> ou vector<std::shared_ptr<Magazine>>). Je vois pas comment on peut s'en passer à moins de ne pas travailler avec des objets à sémantique d'entité (ce qui est peut être le cas de Sean Parent)
Avatar de PilloBuenaGente PilloBuenaGente - Membre éclairé https://www.developpez.com
le 13/09/2013 à 21:21
Citation Envoyé par CedricMocquillon  Voir le message
"on aura un truc comme un vector<Magazine*> ou vector<std::shared_ptr<Magazine>>"

Ou bien vector< magazine_id > avec map< int, Magazine >
#edit Ou bien map< Magazine_id, Magazine >
Avatar de CedricMocquillon CedricMocquillon - Membre averti https://www.developpez.com
le 14/09/2013 à 9:50
@PilloBuenaGente je comprend pas bien ton édit, si on a deux classes comme l'exemple de Wikipedia: Person et Magazine et que l'on veut lister les magazines auxquels une personne est abonnée, soit on stocke dans Person les références aux magazines soit on passe comme tu l'indiquais par des id.
Code : Sélectionner tout
1
2
3
4
5
6
7
8
class Person 
{ 
public: 
  void add(Magazine* m) { magazines_m.push_back(m); } 
  std::vector<Magazine*> const& magazines() const { return magazines_m; } 
private: 
  std::vector<Magazine*> magazines_m; 
};
ou
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
class Person 
{ 
public: 
  typedef size_t magazine_id_t; 
 
  void add(magazine_id_t m) { magazines_m.push_back(m); } 
  std::vector<magazine_id_t> const& magazines() const { return magazines_m; } 
private: 
  std::vector<magazine_id_t> magazines_m; 
};
Par contre maintenant si je veux lister les titres des magazines auxquels une personne est abonnée, par ta méthode c'est bien plus lourd puisqu'il faut que je connaisse le manager (au sens informatique) des magazines. De plus on passe d'un accès en temps constant (O(1)) à un accès en temps logarithmique (la recherche dans la map) ce qui n'est vraiment pas efficace.
Avatar de JolyLoic JolyLoic - Rédacteur/Modérateur https://www.developpez.com
le 14/09/2013 à 12:33
Citation Envoyé par CedricMocquillon  Voir le message
J'ai personnellement du mal à voir comment ça peut être compatible si on a des objets à sémantique d'entité qui sont référencés par d'autres (type association UML) : il faut bien un lien entre la personne et ses magasines (on aura un truc comme un vector<Magazine*> ou vector<std::shared_ptr<Magazine>>). Je vois pas comment on peut s'en passer à moins de ne pas travailler avec des objets à sémantique d'entité (ce qui est peut être le cas de Sean Parent)

Dans son cas, il avait des objets dont il aurait aimé que la sémantique soit une sémantique de valeur, mais pour lesquels il voulait un comportement polymorphe. Le fait de les gérer par pointeur et de dire "maintenant, ils ont une sémantique d'entité" le gênait.

Sa solution s'approche un peu des value_ptr, clone_ptr ou autres (il la développe dans sa deuxième présentation), mais avec la différence qu'il n'impose pas de dériver d'une classe de base. J'avoue que je suis assez tenté de l'essayer à la prochaine occasion.
Avatar de koala01 koala01 - Expert éminent sénior https://www.developpez.com
le 17/09/2013 à 22:33
Citation Envoyé par CedricMocquillon  Voir le message
J'ai personnellement du mal à voir comment ça peut être compatible si on a des objets à sémantique d'entité qui sont référencés par d'autres (type association UML) : il faut bien un lien entre la personne et ses magasines (on aura un truc comme un vector<Magazine*> ou vector<std::shared_ptr<Magazine>>). Je vois pas comment on peut s'en passer à moins de ne pas travailler avec des objets à sémantique d'entité (ce qui est peut être le cas de Sean Parent)

A vrai dire, dés que l'on parle d'entité, on parle d'objet identifiable "de manière unique et non ambigüe".

Bien sûr, l'adresse d'un objet est une des manières qui permettent une telle identification, mais, de manière générale, il y a toujours certaines données qui représentent cette unicité référentielle :

Que ce soit le numéro d'un compte bancaire, le numéro IBAN d'un livre, le numéro de sécu d'une personne ou le numéro de série d'autre chose, tu trouveras toujours une donnée qui te permettra de dire que c'est cet objet là en particulier qui t'intéresse et non celui qui se trouve juste à coté.

Il n'y a strictement rien qui t'oblige à utiliser un (pointeur sur l') objet associé pour représenter l'association : tu peux parfaitement te contenter d'utiliser cet identifiant unique.

C'est d'ailleurs généralement beaucoup plus efficace dans le sens où les associations sont souvent bidirectionnelles et de type "un à plusieurs" : chaque personne peut être abonnée à 0 ou plusieurs magazines, mais chaque magazine peut avoir 0 à plusieurs abonnés.

En ne plaçant que l'identifiant unique du magazine (au niveau de la personne) ou de la personne (au niveau du magazine), voire, en créant une abstraction supplémentaire (qui peut être utilisé comme identifiant unique) qui représente à chaque fois la relation entre une personne bien particulière et un magazine bien particulier, tu te facilite énormément le travail de recherche.

Note d'ailleurs que c'est l'une des règles à respecter au niveau des BDD pour pouvoir dire qu'elles sont normalisées (même si je ne sait plus le niveau de normalisation )
Avatar de CedricMocquillon CedricMocquillon - Membre averti https://www.developpez.com
le 18/09/2013 à 16:13
Je commence à douter de l'approche que j'ai de cette problématique:
Si on prend l'exemple Person / Magazine avec une association plusieurs à plusieurs, on a plusieurs approches possibles:
Celle que j'utilise jusqu'à maintenant utilise des pointeurs, j'ai également un manager de personnes et de magazines qui est utilisé lors de la lecture des data pour mettre à jour les relations. Niveau code ça donne en gros ça:
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 
class Person 
{ 
public: 
  typedef std::size_t id_type; 
  typedef std::vector<std::shared_ptr<Magazine>> magazines_t; 
   
  Person(id_type id) : id_m(id) {} 
   
  id_type id() const { return id_m; } 
   
  void add_magazine(std::shared_ptr<Magazine> const& m) { magazines_m.push_back(m); } 
   
  magazines_t const& magazines() const { return magazines_m; } 
private: 
  id_m; 
  magazines_t magazines_m; 
};
et la classe magazine
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 
class Magazine 
{ 
public: 
  typedef std::size_t id_type; 
  typedef std::vector<std::shared_ptr<Person>> persons_t; 
   
  Magazine(id_type id, std::string title) : id_m(id), title_m(std::move(title) {} 
   
  id_type id() const { return id_m; } 
   
  std::string const& title() const { return title_m; } 
   
  void add_person(std::shared_ptr<Person> const& p) { persons_m.push_back(p); } 
   
  persons_t const& persons() const { return persons_m; } 
private: 
  id_type id_m; 
  std::string title_m; 
  persons_t persons_m; 
};
au niveau des managers, j'ai ça:
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 
class MagazineManager 
{ 
  typedef Magazine::id_type magazine_id_t; 
public: 
  void add_magazine(std::shared_ptr<Magazine> const& m) { magazines_m[m->id()] = m; }  //avec en plus une gestion des collisions de clef avec levée d'exception 
   
  std::shared_ptr<Magazine> const& get_magazine(magazine_id_t const& id) { return magazines_m[id]; } //avec en plus gestion des magazines manquant avec levée d'exception 
   
private: 
   
  std::map<magazine_id_t, std::shared_ptr<Magazine>> magazines_m; 
}; 
 
class PersonManager 
{ 
  typedef Person::id_type person_id_t; 
public: 
  void add_person(std::shared_ptr<Person> const& p) { persons_m[p->id()] = p; }  //avec en plus une gestion des collisions de clef avec levée d'exception 
   
  std::shared_ptr<Person> const& get_magazine(person_id_t const& id) { return persons_m[id]; } //avec en plus gestion des magazines manquant avec levée d'exception 
   
private: 
   
  std::map<person_id_t, std::shared_ptr<Person>> persons_m; 
};
au niveau de la lecture des données, j'ai effectivement besoin que la base soit normalisée, j'ai donc trois tables: Person, Magazine, et Subscriptions. Je commence par peupler mes managers avec les tables person et magazine ensuite lors de la lecture des subscriptions, ça donne en gros ça:
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void read_subscription(std::istream& in, PersonManager const& pm, MagazineManager const& mm) 
{ 
  //lectures des données de la base   
  Person::id_type id_p; 
  Magazine::id_type id_m; 
  in >> id_p >> id_m; 
   
  //récupération des entités via les managers 
  auto const& person = pm.get_person(id_p); 
  auto const& magazine = mm.get_magazine(id_m); 
   
  //mise à jour des relations entre les entités 
  person.add_magazine(magazine); 
  magazine.add_person(person); 
}
Voila pour mon approche, maintenant, on peut effectivement passer par des identifiants uniques: au niveau de la classe Person, on aurait donc une liste d'identifiants de Magazine (donc une liste de Magazine::id_type) et non plus directement les références (au sens large du terme) directes aux objets. L'inconvénient que je vois à cette approche (mais bon j'ai pas trop de recul ;-) ) c'est que pour lister les magazines d'une personne, je vais avoir besoin en plus de la personne, du manager de magazines (sinon comment avoir les objets magazines). Pour moi, je vais passer de ça:
Code : Sélectionner tout
1
2
for(auto const& m: p->magazines()) 
  std::cout << m->title() << '\n';
à quelque chose comme ça:
Code : Sélectionner tout
1
2
for(auto const& idm : p->magazines()) 
  std::cout << mm.get_magazine(idm)->title() << '\n';  //avec mm le MagazineManager
La différence d'écriture n'est pas en soit très importante (en plus ça dépend fortement du choix de nommage) mais par contre je passe d'un accès en O(1)*le nombre de magazines de la personne à un accès en O(log n)*le nombre de magazines de la personne avec n le nombre de magazines dans la Bd!
On pourrait toujours essayer de "limiter la casse" au niveau du manager (en utilisant par exemple des unordered_map) mais on aura toujours un overhead non négligeable.
Avatar de Arzar Arzar - Membre émérite https://www.developpez.com
le 18/09/2013 à 18:25
Attention quand même, dans la conférence Sean Parent utilise une définition assez spécial de pointeur brut. Pour lui un pointeur brut est "a pointer to an object with implied ownership and reference semantics". Par exemple pour lui, std::unique_ptr et std::shared_ptr sont des pointeurs bruts.

Donc il ne met pas en cause les pointeurs d'association, en fait il n'en parle pas du tout.
Avatar de Iradrille Iradrille - Expert confirmé https://www.developpez.com
le 18/09/2013 à 20:24
@CedricMocquillon

Si tes managers possèdent les magazines / personnes, pourquoi avoir des shared_ptr dans tes magazines / personnes ? Des weak_ptr auraient ici leurs utilités.

Ça sens les références cycliques à plein nez, attention.

edit: ou des unique_ptr dans tes managers, et des pointeurs nus dans magazines / personnes.
Avatar de koala01 koala01 - Expert éminent sénior https://www.developpez.com
le 19/09/2013 à 1:22
Sans compter que je ne vois pas en quoi un accès en temps constant(en o(1) ) peut t'apporter quoi que ce soit.

En effet, à moins de savoir que tu veux atteindre le 105eme abonné à un magazine ou le 3emeabonnement d'une personne, l'accès en temps constant ne te sera d'aucune utilité.

Or, justement, pour pouvoir dire que c'est le 105eme abonné à au magazine ou le 3eme abonnement d'une personne, tu auras du... recherché l'abonné ou l'abonnement en question.

Et, si tu ne veux pas devoir passer potentiellement l'ensemble des abonnés / abonnements en revue pour trouver celui qui t'intéresse, il faudra maintenir un tableau trié, histoire par exemple de pouvoir faire une recherche dichotomique (et gagner un peu de temps)

Or, quelle que soit la solution choisie pour garder un tableau trié après insertion, elle est clairement moins efficace que ce que les solutions basées sur des arbres binaires sont susceptibles de donner.

Dés lors, pourquoi ne pas utiliser des structures qui vont bien, comme boost.multi_index qui te permettraient, en une seule collection, d'avoir comme index "primaire" la composée des deux identifiants (personne + magazine) et deux index "secondaires" qui seraient l'identifiant du magazine pour l'un et celui de l'abonné pour l'autre

A priori, comme tu feras beaucoup plus de recherches que n'importe quoi d'autre, tu as tout à gagner à séparer les différentes informations.

Et ce, d'autant plus que cela participe au respect du SRP, car tes personnes / magazines ont sans doute déjà d'autres responsabilités que le simple fait de fournir la liste des abonnements / abonnés
Offres d'emploi IT
Ingénieur analyste programmeur (H/F)
Safran - Auvergne - Montluçon (03100)
Ingénieur H/F
Safran - Ile de France - Moissy-Cramayel (77550)
Ingénieur conception en électronique de puissance H/F
Safran - Ile de France - Moissy-Cramayel (77550)

Voir plus d'offres Voir la carte des offres IT
Contacter le responsable de la rubrique C++