Cette page vous donne les différences entre la révision choisie et la version actuelle de la page.
pointeurs_style_c [2016/04/26 17:18] gbdivers créée |
pointeurs_style_c [2016/04/26 18:24] (Version actuelle) gbdivers |
||
---|---|---|---|
Ligne 24: | Ligne 24: | ||
void f(...); // quoi mettre ici ? | void f(...); // quoi mettre ici ? | ||
- | int i; | + | int* pi; |
f(~i~); | f(~i~); | ||
std::unique_ptr<int> upi; | std::unique_ptr<int> upi; | ||
- | f(~pi~); | + | f(~upi~); |
Ligne 209: | Ligne 209: | ||
+ | ==== Fonction generique ==== | ||
+ | |||
+ | Utilisation de template pour passer les indirections. | ||
+ | |||
+ | <code cpp> | ||
+ | template<class T> | ||
+ | void f(T const& value); | ||
+ | |||
+ | int* pi; | ||
+ | f(pi); | ||
+ | |||
+ | std::unique_ptr<int> upi; | ||
+ | f(upi); | ||
+ | |||
+ | |||
+ | std::shared_ptr<int> spi; | ||
+ | f(spi); | ||
+ | </code> | ||
+ | |||
+ | **Probleme 1** : acces aux objets. Selon le type d'indirection (ref vs pointeur), acces via l'operateur ''.'' ou ''->''. Besoin de surcharger selon les 2 cas. | ||
+ | |||
+ | <code cpp> | ||
+ | template<class T> | ||
+ | void f_ref(T const& value) { // T est une ref | ||
+ | value.do_something(); | ||
+ | } | ||
+ | |||
+ | template<class T> | ||
+ | void f_ptr(T const& value) { // T est un pointeur | ||
+ | value->do_something(); | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | **Probleme 2** : semantique des pointeurs = peut etre nullptr. Donc necessite de verifier les acces avant utilisation. Pas necessaire avec ref. | ||
+ | |||
+ | <code cpp> | ||
+ | template<class T> | ||
+ | void f_ref(T const& value) { // T est une ref | ||
+ | value.do_something(); | ||
+ | } | ||
+ | |||
+ | template<class T> | ||
+ | void f_ptr(T const& value) { // T est un pointeur | ||
+ | assert(value); // check ptr | ||
+ | value->do_something(); | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | **Probleme 3** : gerer l'ownership. Certains n'ont pas l'ownership (ref, raw ptr, weak_ptr, etc) et d'autre oui (unique_ptr, shared_ptr, etc). Difficile de gerer cela avec les template. | ||
+ | |||
+ | Par exemple, si on passe un weak_ptr, il faut locker avant d'utiliser. | ||
+ | |||
+ | <code cpp> | ||
+ | template<class T> | ||
+ | void f_ptr(T const& sp) { // T est un weak_ptr | ||
+ | auto sp { wp.lock() }; // lock en shared_ptr | ||
+ | assert(sp); // check ptr | ||
+ | sp->do_something(); | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Autre exemple, si on veut transmettre l'ownership a une fonction : | ||
+ | |||
+ | <code cpp> | ||
+ | std::unique_ptr<int> up; | ||
+ | f(std::move(up)); // on n'a plus besoin de up ensuite | ||
+ | </code> | ||
+ | |||
+ | **Conclusion** : generique, mais trop de cas particulier a gerer. (+ tous les libs possible) | ||
+ | |||
+ | ==== Les references comme parametre universel ==== | ||
+ | |||
+ | Conserver qu'une seule forme : passage par reference. | ||
+ | |||
+ | <code cpp> | ||
+ | // T est un objet, pas une indirection. Template ou non | ||
+ | |||
+ | void f(T const& ref); // objet non mutable | ||
+ | void g(T& ref); // objet mutable | ||
+ | </code> | ||
+ | |||
+ | Reference apporte les garanties : que l'objet est valide et que la fonction n'a pas l'ownership. Rien a tester dans la fonction et 1 syntaxe a ecrire. | ||
+ | |||
+ | Ensuite, creer des adaptateurs pour chaque type d'indirection, via lambda. | ||
+ | |||
+ | Pour une valeur : | ||
+ | |||
+ | <code cpp> | ||
+ | int i; | ||
+ | f(i); // appel direct | ||
+ | </code> | ||
+ | |||
+ | Pour shared_ptr : | ||
+ | |||
+ | <code cpp> | ||
+ | std::shared_ptr<int> spi; | ||
+ | |||
+ | f(*spi); | ||
+ | |||
+ | [spi](){ f(*spi); }; // asynchrone | ||
+ | </code> | ||
+ | |||
+ | Pour weak_ptr : | ||
+ | |||
+ | <code cpp> | ||
+ | std::weak_ptr<int> wpi; | ||
+ | |||
+ | [spi = wpi.lock()](){ f(*spi); }; // lock lors de la capture | ||
+ | |||
+ | [wpi](){ auto spi { wpi.lock(); if (spi) { f(*spi); } }; // lock lors de l'appel a f | ||
+ | </code> | ||
+ | |||
+ | Pour unique_ptr : | ||
+ | |||
+ | <code cpp> | ||
+ | std::unique_ptr<int> upi; | ||
+ | |||
+ | f(*upi); // appel direct | ||
+ | |||
+ | [p = std::move(upi)](){ f(*p); }; // asynchrone | ||
+ | </code> | ||
+ | |||
+ | Pour raw ptr : | ||
+ | |||
+ | <code cpp> | ||
+ | int* pi; | ||
+ | |||
+ | if (pi) { f(*pi); } // appel direct... dangling possible | ||
+ | |||
+ | [pi](){ if (pi) { f(*pi); } }; // asynchrone... dangling possible | ||
+ | </code> | ||
+ | |||
+ | Not safe, risque de dangling. | ||
+ | |||
+ | etc. | ||
+ | |||
+ | ==== Cas particulier des pointeurs nus ==== | ||
+ | |||
+ | Raw ptr ont semantique "peut etre nullptr". Generalement, le traitement effectue par f pourra se decomponser en 2 partie strictement distincte, selon si nullptr ou non. Il est donc dans ce cas possible de gerer le cas de nullptr en dehors de f et d'utiliser les references (ne peut pas etre nullptr). | ||
+ | |||
+ | <code cpp> | ||
+ | void f_nullptr(); | ||
+ | void f_notnull(T const& ref); | ||
+ | |||
+ | int* pi; | ||
+ | if (pi) { | ||
+ | f_notnull(pi); | ||
+ | } else { | ||
+ | f_nullptr(); | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Pas besoin de passer directement un pointeur, ref est "universel". (les cas ou on doit passer un pointeur doivent rester rare). | ||
+ | |||