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

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

[Débutant] Je veux apprendre à "bien programmer"


Sujet :

C++

  1. #21
    Expert confirmé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Décembre 2003
    Messages
    3 549
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Décembre 2003
    Messages : 3 549
    Points : 4 625
    Points
    4 625
    Par défaut
    le try/finally ça se fait facilement avec C++ avec des macros.
    Y'a un truc pour ça dans boost il me semble.

  2. #22
    Membre éprouvé
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    1 064
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : Belgique

    Informations forums :
    Inscription : Mars 2005
    Messages : 1 064
    Points : 1 053
    Points
    1 053
    Par défaut
    Je ne vois pas en quoi c'est profitable pour l'utilisateur. Pour ma part il m'a fallu quelques années avant d'oser aller jeter un coup d'oeil dans des headers C, et pourtant ce n'est que du C! Aujourd'hui encore je ne le fais que quand je veux comprendre le fonctionnement d'un lib. Pour le reste je vais uniquement voir des tutos ou la doc doxygen du projet (fort heureusement, tous les projets un peu sérieux en ont), c'est bien moins casse-tête.
    Un truc supplémentaire: javadoc n'est pas un juste un outil, il fait partie de la norme java. Et puis il n'y a pas que ça, il y a aussi les explorateurs de classes (mais je n'aime pas trop).

    Citation Envoyé par Aurelien.Regat-Barrel
    Parce que tu demandes le choix entre un objet robuste et un objet que tu peux facilement fouttre en l'air. Mais si ca peut te consoler, finally existe dans C++/CLI et Sutter solutionne la question du "c'est mieux l'un ou l'autre ?" par "c'est mieux les 2". Mais C++/CLI est managé, donc c'est différent. En C++, y'a pas de seconde chance, pas de repechage. Comme il n'y a pas de GC, si on utilisait finally au lieu du RAII, il faudrait l'utiliser 10 fois, 100 fois plus qu'en Java. Imagine la lisibilité du code...
    Ben voila, c'est mieux les deux. Tu as l'air de penser que je voudrais absolument programmer en C++ comme en Java, je serais bien fou de vouloir une telle chose. J'ai beau désirer l'intégration du finally dans la norme, ça ne m'empèche pas de faire des classes nickel chrome quand à la gestion de la mémoire, j'ai juste pas envie de devoir en taper 20 pour utiliser une biblio C dans un projet de petite taille.
    Juste une remarque: ça n'a rien à voir avec le C++/CLI, c'est une fonctionnalité qui existe dans Visual C++ depuis des années. Quelques autres compilateurs le permettent aussi (je me souviens d'un certain "cxx" qu'on utilisait en cours).

  3. #23
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Pour le finally, je n'ai jamais ressenti le besoin de l'utiliser en C++, et je serais contre son introduction si elle était proposée, car pour moi, ça incite à un mauvais style de programmation.

    Pour ce qui est que le RAII prendrait de nombreuses lignes à mettre en place, je ne suis pas d'accord. Avec les shared_ptr, le RAII est monoligne.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    shared_ptr<void> monPointeurC(allocatePointerC(), freePointerC);
    Pour la séparation interface/implémentation, le problème principal n'est pour moi pas tant la lecture (les outils font tout ce qu'on veut) mais à l'écriture, surtout dans les grands projets : Comment confier à un développeur une interface pré-écrite et qu'il n'a pas le droit de modifier sans en référer aux architectes.

  4. #24
    Expert éminent sénior

    Homme Profil pro
    pdg
    Inscrit en
    Juin 2003
    Messages
    5 752
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : pdg

    Informations forums :
    Inscription : Juin 2003
    Messages : 5 752
    Points : 10 683
    Points
    10 683
    Billets dans le blog
    3
    Par défaut
    Et voilà l'article :

    Gérer ses ressources de manière robuste en C++

    qui explique la même chose que l'unique ligne de code Loic, mais en 20 pages

  5. #25
    Alp
    Alp est déconnecté
    Expert éminent sénior

    Avatar de Alp
    Homme Profil pro
    Inscrit en
    Juin 2005
    Messages
    8 575
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Juin 2005
    Messages : 8 575
    Points : 11 860
    Points
    11 860
    Par défaut
    J'ai lu ton article et j'ai laissé un commentaire.

    zais_ethael > je pense que ta vision des choses est quand même déformée par le fait que tu as visiblement une certaine expérience en Java et que tu as visiblement bien plus programmé en Java qu'en C++. Lorsque tu conçois des bibliothèques, tu dois avoir une vision "Java" prépondérante, que tu programmes en C++ ou non.
    A l'inverse, un grand nombre d'entre nous possède à l'inverse un raisonnement fortement influencé par le C++. Cela pèse beaucoup dans la conception, et cela se ressent.
    Pour nous, le RAII est la solution logique au problème du code exception-safe, qu'il s'agisse de wrapper des bibliothèques C ou autres. En Java il en est autrement.
    Important également : en C++, au fur et à mesure de l'apparition des nouvelles techniques comme le RAII, il y a toujours eu des améliorations et expansions de ces techniques, et bien d'autres techniques basées sur celles venant de paraître ont vu le jour. Principalement le RAII, car c'est le sujet principal ici, mais aussi d'autres techniques, ont grandement simplifié la programmation en C++. Il serait par conséquent "absurde" (quoi, j'y vais un peu fort? ) d'introduire par exemple try...finally dont l'utilisation serait bien moins simple que le RAII.

    Pour comparer, tu peux adapter le code de l'exemple de Aurélien avec des blocs try...finally, en imaginant qu'ils aient été intégrés au C++.

  6. #26
    Membre éprouvé
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    1 064
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : Belgique

    Informations forums :
    Inscription : Mars 2005
    Messages : 1 064
    Points : 1 053
    Points
    1 053
    Par défaut
    J'ignorais que shared_ptr contenait cette fonctionnalité, c'est bon à savoir ça
    Néanmoins, pour d'autres raisons, je n'apprécie pas trop cet exemple.
    Je peux comprendre la problématique d'une classe qu'on aurait préféré ne pas être container aware, mais forcer l'utilisation d'un shared_ptr pour les utilisateurs de la biblio n'est pas une bonne idée non plus, surtout quand on parle de sockets!
    Pour expliquer ceci, je vais d'abord faire une supposition: nous savons tous que contrairement à un garbage collector un smart pointer à comptage de réfèrences est déterministe. Néanmoins, en tant que programmeur, est-on vraiment en mesure de connaitre le moment de la destruction de l'objet pointé? Non, il faudrait pour cela surveiller tous les endroits dans le projet où l'objet est susceptible d'être utilisé, ce qui devient bien vite impossible ou témoigne d'une très mauvaise programmation. Il est donc préfèrable de considérer qu'un shared_ptr est non déterministe.
    Donc, dans le cas où le destructeur de l'objet est sensé faire autre chose que de la simple libération de mémoire (ce dont on ne se préoccupe plus trop de nos jours, tant qu'il n'y a pas de fuite), nous n'avons aucun moyen de savoir quand cette action sera réalisée.
    C'est excessivement grave dans le cas de sockets puisqu'il est impossible d'en ouvrir un sur un port déja ouvert. Si pour une raison ou une autre, notre programme doit fermer puis réouvrir des ports de temps à autre (le port d'envoi de données pour un serveur ftp par exemple), on sera bien embêté puisque la réussite de cette action dépendra du déroulement du programme (qu'en tant que bon programmeur nous préfèrerons considérer comme aléatoire).

    Ton exemple est donc un exemple typique où nous préfèrerons laisser l'utilisateur de la biblio gérer le cycle de vie de l'objet à sa guise. L'idéal serait un auto_ptr, parcequ'on peut en faire tout ce qu'on veut contrairement au shared_ptr. L'ennui est que l'auto_ptr ne peut contenir ce foncteur spécial pour la destruction de l'objet. Alors on a deux possibilités:
    - soit on fait quand même un objet containeur aware
    - soit on rajoute une couche supplémentaire entre l'auto_ptr et l'objet, n'ayant d'autre but que d'informer le manager de la destruction.

    Citation Envoyé par JolyLoic
    Pour la séparation interface/implémentation, le problème principal n'est pour moi pas tant la lecture (les outils font tout ce qu'on veut) mais à l'écriture, surtout dans les grands projets : Comment confier à un développeur une interface pré-écrite et qu'il n'a pas le droit de modifier sans en référer aux architectes.
    Il y a des diagrammes UML pour ça, voir les interfaces suivant les cas. Sans oublier que les headers C++ ne contiennent pas uniquement l'interface, il y a aussi les méthodes private. Néanmoins il est vrai que ton point de vue est intéressant.

    En dehors de ça, je me demande si on s'est pas un peu écarté du sujet de départ la. Oo

  7. #27
    Expert éminent sénior

    Homme Profil pro
    pdg
    Inscrit en
    Juin 2003
    Messages
    5 752
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : pdg

    Informations forums :
    Inscription : Juin 2003
    Messages : 5 752
    Points : 10 683
    Points
    10 683
    Billets dans le blog
    3
    Par défaut
    Citation Envoyé par zais_ethael
    Néanmoins, en tant que programmeur, est-on vraiment en mesure de connaitre le moment de la destruction de l'objet pointé? Non, il faudrait pour cela surveiller tous les endroits dans le projet où l'objet est susceptible d'être utilisé, ce qui devient bien vite impossible ou témoigne d'une très mauvaise programmation.
    Dans cet exemple, ResourceManager sait très exactement quand l'objet qu'il a alloué est libéré, sans rien surveiller en particulier, via ResourceDeleter. Ou alors je n'ai pas compris ce que tu voulais dire.
    Cet exemple est tiré d'un cas réel que j'ai codé, où en plus de mettre à jour le compteur de ResourceManager, un signal était émis par le Manager pour signaler à ses observeurs que son état avait évolué. S'il s'agit d'un gestionnaire de téléchargements par exemple, cela permet à tout plein de classes UI de s'abonner à un évènement "le nombre de téléchargements en cours a changé" et de mettre à jour leur affichage. C'était vraiment très souple d'utilisation.

    Citation Envoyé par zais_ethael
    C'est excessivement grave dans le cas de sockets puisqu'il est impossible d'en ouvrir un sur un port déja ouvert. Si pour une raison ou une autre, notre programme doit fermer puis réouvrir des ports de temps à autre (le port d'envoi de données pour un serveur ftp par exemple), on sera bien embèté puisque la réussite de cette action dépendra du déroulement du programme (qu'en tant que bon programmeur nous préfèrerons considérer comme aléatoire).
    Tu peux controler le cycle de vie de tes objets de la même manière avec un shared_ptr qu'avec un pointeur brut. Exemple:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     
    SocketPtr s = SocketManager::New();
    if ( s )
    {
        while ( /* condition */ )
        {
            if ( s->Open( 21 ) ) // port FTP
            {
                // effectuer un transfert
                TransmitFile( s ); // pour l'exemple
                s->Close();
            }
        }
        s.reset(); // ou SocketManager::Free( s );
    }
    Seule la derniere ligne change en fonction que SocketPtr est un shared_ptr ou un pointeur brut Socket*. Par contre, que se passe-t-il si la fonction TransmitFile est buggée et plante en plein milieu, en laissant remonter une jolie exception catchée plus haut ?
    Si c'est un shared_ptr, le port est correctement fermé, le manager est informé de la libération de la Socket, et il est possible de continuer à bosser. Si c'est un pointeur brut, eh bien...

    Citation Envoyé par zais_ethael
    Ton exemple est donc un exemple typique où nous préfèrerons laisser l'utilisateur de la biblio gérer le cycle de vie de l'objet à sa guise. L'idéal serait un auto_ptr, parcequ'on peut en faire tout ce qu'on veut contrairement au shared_ptr. L'ennui est que l'auto_ptr ne peut contenir ce foncteur spécial pour la destruction de l'objet. Alors on a deux possibilités:
    - soit on fait quand même un objet containeur aware
    - soit on rajoute une couche supplémentaire entre l'auto_ptr et l'objet, n'ayant d'autre but que d'informer le manager de la destruction.
    Je ne comprends pas ce que auto_ptr permet de faire que shared_ptr ne peut pas. Pour la seconde solution, c'est précisement le rôle de ResourceDeleter. Tu as peut être mal compris cela, si c'est le cas dis moi ce qui n'est pas clair, que j'essaye d'améliorer

  8. #28
    Membre éprouvé
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    1 064
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : Belgique

    Informations forums :
    Inscription : Mars 2005
    Messages : 1 064
    Points : 1 053
    Points
    1 053
    Par défaut
    Avec un shared_ptr, c'est comme si ton objet était contenu dans un espace de la mémoire sur lequel tu n'as aucune prise, on ne peut le détruire, on ne peut le transférer dans un autre type de smart pointer. Ceci est parfaitement logique si on imagine un peu les difficultés de sa conception. Le seul moyen de de détruire l'objet est de faire tomber toutes les réfèrences, ce qui est difficile à gérer et il est préfèrable de ne pas s'y essayer. Voila la raison pour laquelle je considère qu'ils ne devraient être utilisés que dans un but de libération de mémoire pur et simple.

    Je n'ai jamais parlé de pointeur brut, c'est de toutes façons une mauvaise idée d'en renvoyer en argument d'une fonction. Dans ce genre de cas, je préfèrerais utiliser un auto_ptr parcequ'ils sont géniaux pour opérer des transferts de gestion de cycle de vie. Quand l'utilisateur reçoit un auto_ptr, il sait qu'il est le seul à gérer l'objet et qu'il peut en faire ce qu'il veut. Si il juge qu'il a besoin d'un shared_ptr, il peut simplement faire shared_ptr<bidule> machin(auto.release()), la différence est que c'est l'utilisateur de ta biblio qui décide, toi tu te contente de faire une fonction qui fonctionnera même si il oublie de récupérer le résultat.

    Si dans ton exemple, tes objets ressources avaient par exemple été des images (grosse occupation de mémoire), je ne dis pas, obliger l'utilisateur à utiliser un shared_ptr ne portera probablement pas à conséquences. Mais dans le cas de sockets, où la libération du port dépend de la destruction de l'objet, il serait plus simple de conserver une entité unique pour gérer le cycle de vie de l'objet. Et c'est plus difficile avec un shared_ptr.

    PS: Oulala, que je n'aime pas les typedefs sur des pointeurs.

  9. #29
    Expert éminent sénior

    Homme Profil pro
    pdg
    Inscrit en
    Juin 2003
    Messages
    5 752
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : pdg

    Informations forums :
    Inscription : Juin 2003
    Messages : 5 752
    Points : 10 683
    Points
    10 683
    Billets dans le blog
    3
    Par défaut
    Citation Envoyé par zais_ethael
    Le seul moyen de de détruire l'objet est de faire tomber toutes les réfèrences, ce qui est difficile à gérer
    Tu y arrives pourtant très bien en Java non ?

    Citation Envoyé par zais_ethael
    Mais dans le cas de sockets, où la libération du port dépend de la destruction de l'objet, il serait plus simple de conserver une entité unique pour gérer le cycle de vie de l'objet. Et c'est plus difficile avec un shared_ptr.
    Hum... Je comprends enfin ce qui te chagrine : "la libération du port dépend de la destruction de l'objet". Ce n'est pas le cas, rien ne t'empeche de coder une fonction Close(). Par contre, dans mon exemple, le manager n'en sera pas informé, je pense que c'est ce qui te gêne.

    Le fait est... que c'est volontaire. J'en ai pas trop parlé dans mon article, mais la sémantique de mon manager avec nombre limité d'instance est de gérer l'attribution des ressources, pas leur usage. Une fois qu'une ressoure t'es accordée, rien ne t'empêche de l'utiliser plusieurs fois, si elle le permet.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     
    SocketPtr s = SocketManager::New();
    if ( s )
    {
        while ( /* condition */ )
        {
            if ( s->Open( 21 ) ) // port FTP
            {
                // effectuer un transfert
                TransmitFile( s ); // pour l'exemple
                s->Close();
            }
        }
        s.reset(); // ou SocketManager::Free( s );
    }
    Dans cet exemple, je peux utiliser plusieurs fois ma Socket sans en redemander une a chaque fois. Par exemple, si mon téléchargement est brusquement interrompu, je peux tenter de réouvrir la Socket et de le reprendre, et ceci plusieurs fois dans une limite de 5 tentatives par exemple.
    Dans un contexte multithread, s'il fallait effectuer 4 appels à New() supplémentaires pour reprendre le téléchargement, un autre thread pourrait piquer la Socket qui vient dêtre fermée... Un autre point est qu'il faudrait effectuer 5 appels systeme au lieu d'un seul, ce qui en fonction de la ressource peut etre couteux et considéré comme du gaspillage si elle est facilement réutilisable une fois créée.

  10. #30
    Membre éprouvé
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    1 064
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : Belgique

    Informations forums :
    Inscription : Mars 2005
    Messages : 1 064
    Points : 1 053
    Points
    1 053
    Par défaut
    Citation Envoyé par Aurelien.Regat-Barrel
    Tu y arrives pourtant très bien en Java non ?
    Bien sur que non. En java on se préocuppe de temps en temps de ne pas laisser des réfèrences sur des objets devenus inutiles histoire de ne pas encombrer la mémoire mais sans plus, il n'y a aucun destructeur de toutes façons, donc aucun interet de connaître le moment de destruction d'un objet. Ca veut aussi dire, par exemple pour un stream java, qu'on sera obligé d'appeler sa méthode close() et ceci toujours dans un try/finally sous peine d'avoir un "oubli" de fermeture si une exception est lancée.

    Citation Envoyé par Aurelien.Regat-Barrel
    Hum... Je comprends enfin ce qui te chagrine : "la libération du port dépend de la destruction de l'objet". Ce n'est pas le cas, rien ne t'empeche de coder une fonction Close(). Par contre, dans mon exemple, le manager n'en sera pas informé, je pense que c'est ce qui te gêne.
    Une méthode close() ne serait qu'une facilité, exactement comme celle de fstream, mais il ne faut en aucun cas ne compter que sur cette méthode (et si une exception était lancée à la ligne juste avant son appel?). A moins de créer une classe supplémentaire pour l'appeler avec du RAII (un peu comme avec les locks de boost::thread), il faut principalement compter sur l'appel au destructeur.

    Sinon, pour ton exemple, je croyais que le fait de compter le nombre d'instances était juste ludique (puisqu'en pratique, ben... ça sert à rien) et qu'en appliquant ce principe aux sockets le ressource manager aurait pour but de vérifier si un port est libre ou non.
    Apparament, ton système a plutot pour but de créer un pool de connexions, les threads n'y ayant pas droit devant se retrouver en attente. Suivant la façon dont est programmée ton applic, il est possible que toutes les ressources finissent par être libérées à un moment où un autre. Mais en ne controlant pas leur cycle de vie par toi même, il est probable qu'elles ne soient pas libérés pile au moment ou tu n'en as plus besoin, ce qui peut nuire aux performances.

Discussions similaires

  1. Apprendre a bien programmer (proprement)
    Par Christophe Genolini dans le forum Langages de programmation
    Réponses: 2
    Dernier message: 08/03/2010, 23h00
  2. je veux apprendre la programmation quel language choisir??
    Par existance dans le forum Débuter
    Réponses: 26
    Dernier message: 06/08/2002, 05h32

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo