IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

GoingNative 2013 - Sean Parent - C++ Seasoning

Le , par germinolegrand

70PARTAGES

Sean Parent - C++ Seasoning
GoingNative 2013

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


Rediffusion de la conférence

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
Conférence suivante : Andrei Alexandrescu - Ecrire du code rapide en C++, rapidement

Et vous,
Qu'en pensez-vous ?

Une erreur dans cette actualité ? Signalez-nous-la !