pointeurs intelligents ,référence et pointeur nu

Peut on utiliser des pointeurs nu ou des référence lorsqu'on ne souhaite pas faire de modifications sur les données pointées ?

Le problème exposé dans ce sujet a été résolu.

Bonjour à tous, J’ai entendu dans une conférence que l’on pouvait utilisé (voir même qu’il était préférable) des références ou des pointeurs nu à la place des pointeurs intelligents lorsque l’on ne souhaitait pas faire de modifications et que l’on ne souhaitait pas gérer la durée de vie des ressources.
Voilà j’aimerais avoir votre avis sur cette question ,car j’ai toujours lu qu’il faillait privilégier les références et les pointeurs intelligents.
Bonne journée estivale à tous.(surtout pour ceux qui sont en vacances ;) ).
NB : la conférence en question

+0 -0

Lu’!

Il vaut mieux utiliser des références en priorité et si la ressource est optionnelle, tu peux utiliser un pointeur nu. L’idée est que si le point d’entrée en question n’a pas de raison de modifier l’existence de la ressource (ce qui HYPER commun), mieux vaut passer un accès direct à la ressource plutôt que de passer un pointeur intelligent qui donnerait un pouvoir supérieur sur la ressource à la fonction.

Si tu passes une référence sur le pointeur intelligent plutôt qu’une référence sur la ressource, tu introduis deux points négatifs :

  • le pointeur intelligent peut être altéré,
  • tu payes deux indirections lorsque tu fais un accès.

La référence empêche cela, et le pointeur nu permet de ne pas transférer de ressource si elle est effectivement optionnelle.

Salut ,et merci de m’avoir répondu ,j’ai encore quelques questions :

modifier l’existence de la ressource

Tu veux dire la détruire ?
Et peut on étendre cette remarque à toute une classe en mettant la dite référence ou pointeur comme attribut d’une classe.

+0 -0

modifier l’existence de la ressource

Tu veux dire la détruire ?

Octodex

Oui, où la changer :

1
2
3
4
5
6
7
void foo(std::unique_ptr<int>& p){
  p.reset(nullptr); //pouf
}
void bar(std::unique_ptr<int>& p){
  auto pp = std::make_unique<int>(42);
  p.swap(pp); //paf
}

Et peut on étendre cette remarque à toute une classe en mettant la dite référence ou pointeur comme attribut d’une classe.

Octodex

Oui, à condition de faire un peu attention à ce que l’on fait avec les durées de vie. Typiquement, avoir une structuration en mémoire sous la forme d’un DAG est largement préférable (et quand on a du multi-threading, il faut faire attention à la gueule du DAG).

Le pointeur nu est possible (voire à privilégier), si la ressource est gérée ailleurs et que tu es sûr qu’elle sera toujours valide tant que tu utiliseras le pointeur nu.

Dans ton jeu, tu as une classe qui gère le chargement des images, donc cette classe va avoir un vector (ou map) de unique_ptr contenant les images chargées et qui retournera une référence ou un pointeur nu vu que les pointeurs retournés seront valides pendant toute la durée du programme (si on part du principe que tu ne "nettoies" jamais ce vector). Utiliser ici des shared_ptr serait overkill.

Le pointeur nu est possible (voire à privilégier)

minirop

Il faut peut etre insister sur un point. Comme l’a dit Ksass`Peuk : "si la ressource est optionnelle, tu peux utiliser un pointeur nu". C’est la grosse difference semantique entre une reference et un pointeur. Cela implique en toute rigueur qu’un pointeur DOIT TOUJOURS etre teste avant utilisation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void foo(int* p) {
    if (p) {
        // use p
    } else {
        // DON'T USE p !!!
    }
}

void bar(int& p) {
    // use p
}

On voit alors vite le problème : si on a pleins de fonctions qui s’appelle mutuellement, alors il faut que chaque fonction teste le pointeur. C’est lourd. Si on ne teste pas, c’est a dire que la fonction assume que le pointeur n’est pas nul, alors il n’y a aucune raison d’utiliser un pointeur, il faut utiliser une référence.

Il y a une très mauvaise habitude a perdre, c’est d’utiliser les pointeurs par défaut en C++. En C, on n’a pas le choix, il n’existe que des pointeurs. En C++, on a le choix. Il faut utiliser l’outil adapté pour chaque utilisation.

EDIT: Concernant les références vs les pointeurs intelligents, il faut remarquer que les références sont plus "universelles" que les pointeurs intelligents, dans le sens ou il est est beaucoup plus facile d’appeler une fonction prenant en paramètre une référence qu’une fonction prenant en paramètre un pointeur intelligent.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void foo(std::unique_ptr<int> const& p) { ... }

int main() {
    auto up = std::make_unique<int>(123);
    foo(up); // ok

    auto sp = std::make_shared<int>(123);
    foo(???);

    auto p = new int { 123 };
    foo(???);

    auto i { 123 };
    foo(???);

L’inverse est facile, au pire en créant des wrappers via des fonctions lambda.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
void foo(int const& p) { ... }

int main() {
    auto i { 123 };
    foo(i); // ok

    // sans transfert d'ownership
    auto up1 = std::make_unique<int>(123);
    [](std::unique_ptr<int> const& up){ foo(*up.get()); }(up1); // ok

    // avec transfert d'ownership
    auto up2 = std::make_unique<int>(123);
    [up = std::move(up2)](){ foo(*up.get()); }(); // ok

    auto sp = std::make_shared<int>(123);
    [](std::shared_ptr<int> const& sp){ foo(*sp.get()); }(sp); // ok

    auto p = new int { 123 };
    foo(*p); // ok
}

(il est même possible d’utiliser auto dans les lambdas, pour utiliser la même fonction lambda pour passer des shared ou des unique ptr)

Donc, pour conclure : references > pointeurs intelligents > pointeurs nus

+1 -0

Cela implique en toute rigueur qu’un pointeur DOIT TOUJOURS etre teste avant utilisation.

Je ne suis pas d’accord. Un paradigme courant est de considérer que c’est à l’appelant de s’assurer que le pointeur qu’il passe est bien valide (car de toute façon, un pointeur NULL n’est qu’un cas particulier mais courant d’un pointeur invalide). Du coup cette vérification se fait à l’allocation et non à chaque fonction où le pointeur est utilisé.

Après suivant la taille dudit objet, considérer le pointeur comme valide par défaut n’est pas forcément idiot. En effet, si mon logiciel ne peut pas allouer 4kio de mémoire, il est probable que la machine soit dans une situation difficilement tenable et que de sauver les meubles est plus compliqué que pertinent. Mais cela dépend d’un tas de choses, ma remarque n’est pas universelle mais vérifier systématiquement me paraît exagéré suivant les situations.

Il y a une très mauvaise habitude a perdre, c’est d’utiliser les pointeurs par défaut en C++. En C, on n’a pas le choix, il n’existe que des pointeurs. En C++, on a le choix. Il faut utiliser l’outil adapté pour chaque utilisation.

En Qt tu fais comment pour te débarrasser des pointeurs présents partout dans l’API ?

+0 -0

@gbdivers j’ai bien vu merci :) .

Du coup cette vérification se fait à l’allocation et non à chaque fonction où le pointeur est utilisé.

@Renault : Sauf que si le pointeur est devenu invalide ou que la mémoire a été libérée entre temps tu vas avoir une bonne grosse erreur.

+0 -0

@Renault : Sauf que si le pointeur est devenu invalide ou que la mémoire a été libérée entre temps tu vas avoir une bonne grosse erreur.

C’est à l’appelant de vérifier que ce qu’il envoie est valide, pas au receveur selon moi. Car dans la quasi totalité des cas, une fois que le pointeur est alloué correctement, il est valide pour tous les appels jusqu’à sa fin de vie (et dans ce cas là, on appelle plus de fonctions avec).

À moins de détruire tes pointeurs à la sauvage, en général cela se résout très bien sans devoir vérifier de partout.

+0 -0

Un paradigme courant est de considérer que c’est à l’appelant de s’assurer que le pointeur qu’il passe est bien valide (car de toute façon, un pointeur NULL n’est qu’un cas particulier mais courant d’un pointeur invalide). Du coup cette vérification se fait à l’allocation et non à chaque fonction où le pointeur est utilisé.

Renault

Je précise ce que j’ai voulu dire.

Pour le cas ou le pointeur n’est pas nul, mais n’est pas valide, on va vite mettre de côté : on ne peut pas tester si le pointeur est valide et son utilisateur produit un UB. C’est un cas qui ne doit pas arriver.

On a le cas ou le pointeur peut etre null ou etre valide. Comme on ne peut pas utiliser un pointeur null, on aura dans ce cas (sauf fonction qui ne fait rien d’autre que d’appeler une autre fonction, mais ça, osef) un test pour choisir entre un traitement avec un pointeur valide ou un traitement sans le pointeur. Pas de problème avec ça, c’est la sémantique des pointeurs.

Le second cas est donc un pointeur qui ne doit pas etre nul. Effectivement, c’est une situation tout à fait acceptable. C’est simplement une pré-condition à la fonction, on met un assert pour tester la validité en debug, tout va bien.

Mais a quoi ça sert d’utiliser un pointeur avec une pré-condition "ne doit pas etre null" plutot qu’une reference ? (Qui est aussi une indirection "qui ne peut pas etre null", mais la précondition est explicite). La différence entre pointeur et référence est justement cette precondition "ne peut pas etre null". Utiliser un pointeur alors que la référence fait exactement la meme chose n’a a priori pas de sens. C’est donner une liberté au code appelant, qui peut laisser passer une erreur, surtout si on ne la teste pas.

En Qt tu fais comment pour te débarrasser des pointeurs présents partout dans l’API ?

Renault

Tu ne peux pas. Et du coup, tu paies la faiblesse des pointeurs par rapport aux références, ce qui se traduit par avoir plus souvent des erreurs sur les pointeurs.

(En pratique, je vois plus souvent des problèmes de dangling pointers sur le objets Qt, du fait de pointeurs non initialisé ou non remis à nullptr, que des problèmes d’appels de fonctions avec un pointeur null alors que l’on attend un pointeur non null. La raison est qu’il est très facile de détecter ce problème avec un assert et que peu de ce type d’erreur passe en release. Mais comme toujours dans ce cas de figure, si une erreur passe en release, le debug est plus complexe, parce que si on ne l’a pas vu avant, c’est que c’est un cas tordu)

Bref, Qt a des problemes a cause de son heritage historique. On doit faire avec, même si cela complexifie la maintenance du code.

+0 -0

Je pense qu’il est préférable de tester les préconditions (ici le pointeur) ,c’est toujours plus sûr que de ne pas le faire surtout quand ce sera quelqu’un d’autre qui utilisera tes fonctions.

Octodex

La seule partie de la précondition sur un pointeur nu que tu veux tester, tu ne peux pas la tester. La seule chose que tu peux tester c’est la présence ou non de la ressource, donc pas une précondition.

Je pense qu’il est préférable de tester les préconditions (ici le pointeur) ,c’est toujours plus sûr que de ne pas le faire surtout quand ce sera quelqu’un d’autre qui utilisera tes fonctions.

Comme je l’ai dit, c’est une question de point de vue. Ici tu sembles sous-entendre que c’est de la responsabilité de la fonction appelée de s’assurer que ce qu’on lui fourni est correct. Je pense personnellement que non, la fonction est documentée, si tes paramètres sont invalides de manière trop grossière (genre un pointeur mal alloué), bah tant pis pour toi ça crashera.

C’est d’ailleurs tout le principe de la programmation par contrat, de considérer que l’appelant demande un service et qu’il est responsable de fournir des arguments corrects. D’autant plus qu’un pointeur non nul peut être invalide aussi et tu n’as aucun moyen de t’en assurer.

Il n’y a pas de réponses absolues à ces questions, c’est souvent une question de point de vue, d’habitudes et cela dépend aussi du contexte (parfois oui, il faudra que tu vérifies les paramètres donnés). Des compromis en somme. En tout cas tu trouves ces deux idées un peu partout et chaque camp a des arguments valables.

Bref, Qt a des problemes a cause de son heritage historique. On doit faire avec, même si cela complexifie la maintenance du code.

Je sais, ce que je voulais te faire souligner c’est que le C++ a un héritage, des frameworks puissants, complexes mais qui imposent aussi leur style. Qt est loin d’être une bibliothèque mineure dans cet écosystème, d’ailleurs j’ai plus codé en C++ avec que sans. Du coup cela t’impose l’usage des pointeurs, même si tu voudrais faire autrement.

+0 -0

@Ksass `Peuk Dire que le pointeur doit être non nul c’est une précondition .Je ne vois pas pourquoi ce ne serait pas une précondition.

Octodex

Si tu veux un pointeur non nul, ce que tu veux c’est une référence, parce que ta ressource n’est pas optionnelle. Et si ta ressource est optionnelle, le cas nul est acceptable, donc ton pointeur doit être soit nul, soit valide, ce qui va impliquer deux traitements différents, la non-nullité n’est pas donc pas une pré-condition, ce n’est que l’un des cas.

Je sais, ce que je voulais te faire souligner c’est que le C++ a un héritage, des frameworks puissants, complexes mais qui imposent aussi leur style. (…) Du coup cela t’impose l’usage des pointeurs, même si tu voudrais faire autrement.

Renault

Quand tu passes un pointeur a une fonction, en considérant que le pointeur ne doit pas etre nul, quelle est la contrainte technique dans Qt qui empêcherait d’utiliser une référence ?

1
2
3
4
5
6
7
void foo(QObject* p) {
    p->doSomething(); // assume p is valid
}

void foo(QObject const& p) {
    p.doSomething(); // p is valid!
}

Cela rejoint ce que je disais avant :

Il y a une très mauvaise habitude a perdre, c’est d’utiliser les pointeurs par défaut en C++. En C, on n’a pas le choix, il n’existe que des pointeurs. En C++, on a le choix. Il faut utiliser l’outil adapté pour chaque utilisation.

Ici, on va utiliser les pointeurs avec Qt par habitude le plus souvent, pas parce que c’est le meilleur choix technique.

D’ailleurs, on pourrait aussi utiliser QPointer au lieu de pointeurs nus, ce qui est meilleur en termes de garanties sur la gestion de la mémoire. Et encore une fois, on ne le fait pas pour des raisons d’habitudes plus que pour des raisons techniques.

Mon propos n’est pas de dire que les pointeurs nus ne sont plus utiles (je te rappelle que je réagissais au message de minirop, qui disant "Le pointeur nu est possible (voire à privilégier)"), surtout avec les contraintes de maintenir du code ancien et/ou en fonction des libs utilisées. Mais il faut reconnaître qu’il y a aussi un problème d’habitudes de programmation, qui fait que les gens ne choisissent pas l’outil le plus adapté à une situation.

+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