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 :

Un constructeur qui ne construirait pas


Sujet :

C++

  1. #1
    Membre confirmé

    Inscrit en
    Octobre 2003
    Messages
    209
    Détails du profil
    Informations forums :
    Inscription : Octobre 2003
    Messages : 209
    Par défaut Un constructeur qui ne construirait pas
    Bonjour.

    J'aimerais savoir s'il est possible qu'un constructeur, si une conditon n'est pas validée, ne crée pas l'instance ?
    Par 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
    16
    17
     class Personne
    {
    public:
      ... 
      Personne()=delete; //pour ne pas utiliser le constructeur pas défaut
      Personne(std::string nom; std::string prenom);
      ...
    private:
      ...
      std::string nom;
      std::string prenom;
     ...
    }
     
    ...
    Personne personne1 {"","toto"};
    ...
    le constructeur voit que nom est vide et donc ne crée pas l'instance.

    Est-ce possible? sinon comment faut-il procéder?

    Olivier]

  2. #2
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 199
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 199
    Billets dans le blog
    4
    Par défaut
    Tu peux lancer une exception, ou utiliser une factory.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  3. #3
    Expert confirmé
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 328
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 328
    Par défaut
    Note: De base, si tu définis le moindre constructeur, le constructeur dit "par défaut" (i.e. sans paramètres) n'est plus généré. Techniquement, le = delete est redondant ici.

    Tu veux interdire Personne("", "toto"), c'est ça?
    Peut-être avec de la méta-prog on peut reconnaitre le cas où tu reçois un "" vide, mais ça ne vaut que pour les littéraux, pas pour les char const* vers des "", ou même des chaines vides. Ca va vite être compliqué pour pas grand chose AMA.

    En m'inspirant de gsl::not_null<>, ça pourrait ressembler à ça: https://godbolt.org/z/6PrYMoseq -- la partie meta-prog est très certainement simplifiable (je la trouve compliquée à cause de l'ambiguité entre la référence vers tableau de caractères et le pointeur vers caractères, j'ai fait au plus rapide) ; j'exploite aussi très certainement des notions peu familières: référence vers tableau VS pointeur, require, static_assert, héritage privé, invariant garantis par construction ; et ça manque possiblement d'explicit.


    Ton type Personne serait ensuite construit à partir de 2 non_empty_string.

    Par contre... ces machins dès qu'on les déplace (tu verras ça plus tard, ou dans un autre cours post-débutant), ils ne seront plus garantis non-vides...

    Bref, la conclusion: ici des exceptions, c'est bien
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  4. #4
    Expert confirmé
    Avatar de Mat.M
    Profil pro
    Développeur informatique
    Inscrit en
    Novembre 2006
    Messages
    8 659
    Détails du profil
    Informations personnelles :
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Novembre 2006
    Messages : 8 659
    Par défaut
    comme l'écrit Bousk il faut gérer une exception.

  5. #5
    Expert confirmé
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    4 999
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations professionnelles :
    Activité : Lead Dev Python
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Juillet 2006
    Messages : 4 999
    Par défaut
    Hello,

    comme l'écrit Bousk il faut gérer une exception.
    Ah mais Bousk ne dit pas "il faut", pourquoi il faut ?

    Moi je vois plein d'autres solutions, par exemple en C++ 17 on pourrait utiliser std::optional non ?

    ou via un pointeur intelligent ?
    Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
    La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

  6. #6
    Expert confirmé
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 328
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 328
    Par défaut
    Salut,
    Citation Envoyé par fred1599 Voir le message
    Moi je vois plein d'autres solutions, par exemple en C++ 17 on pourrait utiliser std::optional non ?

    ou via un pointeur intelligent ?
    Ha! Bien vu.

    Mais... ne serait-pas une erreur de programmation dans le contexte de son désign? Du coup plutôt que d'introduire du code à vocation de traiter des erreurs de progs, quid d'une assertion des familles?
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  7. #7
    Expert confirmé
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    4 999
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations professionnelles :
    Activité : Lead Dev Python
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Juillet 2006
    Messages : 4 999
    Par défaut
    Vous connaissez le besoin du PO ? Et que comprenez vous de ce "design" ?

    Si la condition détermine si l'objet doit être créé ou ne pas exister du tout, je trouve que std::optional plutôt intéressant, en tout cas une solution à proposer...
    Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
    La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

  8. #8
    Membre confirmé

    Inscrit en
    Octobre 2003
    Messages
    209
    Détails du profil
    Informations forums :
    Inscription : Octobre 2003
    Messages : 209
    Par défaut
    Bonjour et merci pour toutes vos réponses.

    @Bousk, utiliser une exception, ça veut dire "lancer" un throw dans le constructeur et utiliser un catch dans la procédure appelante ?
    Si c'est bien ainsi qu'il faut procéder, le throw interrompt-il effectivement la création ?
    Ce qui me gêne dans cette solution, c'est qu'il faut gérer quelque chose au niveau de l'appelant. Et c'est précisément ce que je voudrais éviter.

    @Luc, si j'ai bien compris ce que tu as fait, on lève l'erreur à la compilation, c'est ça ?
    j'ai noté pour le =delete

    @fred1599, je n'ai pas vraiment compris le std::optional, quant au pointeur intelligent, je vais déjà commencer par maîtriser le pointeur basique

    En fait, l'objectif est de gérer au sein même de la classe la création, ou non, d'un instance selon que les données sont valides ou pas.
    De cette manière l'appelant n'a pas à s'occuper de tester la validités des données.
    Bonne ou mauvaise idée ?

  9. #9
    Expert confirmé
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 328
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 328
    Par défaut
    Le seul moyen existant permettant d’interrompre une construction initiée (i.e. un constructeur est appelé dans le code), c'est avec les exceptions (même chose pour les opérateurs). Il n'y a pas le choix, il n'y a pas d'autre moyen en C++.

    Si tu veux poser une précondition sur la construction de tes classes, l'interruption peut sembler à juste titre bancale: car c'est toujours au code appelant de se mettre en condition de respect des préconditions pour ne pas appeler si quelque chose ne va pas, et en interrompant, ce n'est pas du tout ce que l'on fait. Tu ne vas pas aller à la boulangerie avec un billet de monopoly. C'est à toi, l'appelant de passer des billets tels qu'attendus par le système.

    En suivant cette logique, on arriverait d'une certaine façon aux fonctions de création (parfois appelée factory -- mais on n'est pas dans le Design Pattern factory). C'est là qu'on arrive aux autres solutions évoquées qui consistent à renvoyer des std::optional en C++, à défaut de std::expected (ceci dit, boost offre boost::outcome qui est quand même plus correct pour signaler des erreurs que optional ; cf. #5 ici: https://fsharpforfunandprofit.com/po...d-programming/ ), ou même des pointeurs nuls (ce qui n'est pas compatible avec une possible volonté de vouloir manipuler par valeur).

    Au fond, on n'a pas vraiment résolu le problème que tout ça c'est de la programmation. A un moment donné le code appelant va récupérer des choses qui disent pouvoir échouer, et pour bien faire, il faudrait le traiter: catch, ou vérification si c'est déréférençable/si la capsule obtenue porte bien une valeur. Même si par construction on sait que l'on a tout bien écrit, il faudrait tester si jamais il y a une erreur de programmation dans le code et la traiter par code...

    Si on ne veut pas traiter par programmation le cas d'un appel moisi, il faut s'y prendre autrement. L'approche la plus classique, consiste à passer par des types renforcés (strong type), ici il en faudrait un dont le nom porte cette notion de chaine jamais vide. Ainsi, on déporte bien à l'appelant de ton constructeur de s'assurer que les chaines passées soient bien jamais vides. Chose que la présence de précondition devrait nous pousser à faire. C'est par construction que l'on saura qu'il n'y a pas de questions à se poser -- comme une certaine famille de démonstrations en maths.

    C'est ce que j'ai fait avec mon expérience, même si au fond elle est bancale pour plusieurs raisons:
    - il est impossible de faire détecter à la compilation tous les cas d'erreurs possibles -- seul le cas du littéral "" est détectable à la compilation.
    - les constructeurs qui ne prennent pas des littéraux, devraient être explicit -- pour forcer l'appelant à valider qu'il voit bien qu'il passe une chaine qu'il est censée avoir validée comme non vide ; cf le principe expliqué ici: https://www.cppstories.com/2017/10/notnull/ ,
    - et on a un problème non-trivial: il demande une réflexion quant aux invariants dans le cas des objets moved-from, et il n'y a pas de conclusion triviale et indiscutable.

    Et parce que tout ça est bien compliqué... finalement, lancer une exception en première instance est une chose que j'accepterai ici. Même si d'un point de vue désign c'est quelque chose qui ne me plait pas (cf les 3 billets sur la programmation par contrat dans mon blog).


    > De cette manière l'appelant n'a pas à s'occuper de tester la validités des données.
    > Bonne ou mauvaise idée ?

    Non. Donc. Mon avis là dessus, c'est que les inputs doivent être validées au plus tôt pour que l'on n'ait jamais plus besoin de les tester à l'intérieur du programme. Être dans une zone où on a l'invariant je sais que mes inputs sont validées est ce qui va nous permettre d'écrire du code simple, et de ne pas l'obscurcir de tests de programmation excessivement défensive dans tous les sens.

    Entre Ask for permission VS Ask for forgiveness, Python préfère l'approche de demander pardon. Je préfère définitivement l'autre approche.
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  10. #10
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 199
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 199
    Billets dans le blog
    4
    Par défaut
    Citation Envoyé par fred1599 Voir le message
    Si la condition détermine si l'objet doit être créé ou ne pas exister du tout, je trouve que std::optional plutôt intéressant, en tout cas une solution à proposer...
    Et dans ce cas tu n'es pas dans le constructeur et tombes dans le cas d'une factory.
    Dans le constructeur, l'objet est déjà alloué et se fait initialiser, tu peux pas delete this ou décider que rien ne sera retourné, tu as juste le droit de lancer une exception pour abort.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  11. #11
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Chercheur d'emploi
    Inscrit en
    Septembre 2007
    Messages
    7 627
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur d'emploi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 627
    Par défaut
    Bonjour,

    Citation Envoyé par olivier] Voir le message
    @Bousk, utiliser une exception, ça veut dire "lancer" un throw dans le constructeur
    Oui.

    …et utiliser un catch dans la procédure appelante ?
    Pas forcément dans la procédure appelante. C'est tout le principe de la chose. Tu peux mettre un gros catch à beaucoup plus haut niveau si tu veux pouvoir « rattraper » l'erreur ou au moins la contenir, par exemple en revenant au menu principal ou en prenant des mesures pour terminer proprement. Sinon, tu peux laisser l'exception s'échapper jusqu'au premier niveau et se terminer ton programme. Tu obtiendras un SIGABRT et l'effet sera à peu près le même qu'une erreur dans un langage interprété.

    Par contre, il faut veiller à ce que les ressources allouées, s'il y en a, ne restent pas en suspens. En C++, la manière la plus propre de le faire consiste en général à soigner le RAII à la base, pour que les objets qui meurent de leur belle mort (en quittant le bloc où ils sont instanciés) fassent le ménage d'eux-mêmes.

    Si c'est bien ainsi qu'il faut procéder, le throw interrompt-il effectivement la création ?
    Oui, et c'est même nécessaire dans ce type de situation ainsi que dans une poignée de cas similaires (sauf à utiliser d'emblée des types dont le domaine couvre l'intégralité des cas possibles). Si tu instancies en début de bloc une variable de la forme :


    … alors, même sur un plan purement syntaxique, la variable « foo » est réputée exister juste après le point-virgule. Il n'est pas possible dans cette situation de renvoyer un quelconque code de retour pour indiquer que quelque chose s'est mal passé. Et en plus, c'est justement pour éviter d'avoir à gérer systématiquement ces codes que l'on recourt aux exceptions, ce qui est précisément l'objectif que tu vises.

    Toute la philosophie des exceptions réside justement dans le fait de considérer qu'en principe, tout fonctionne toujours, et de traiter au cas par cas les « exceptions au cas général ». Exemple-type : la division par zéro (dont tout le concept en question est probablement originaire, à mon avis).

    Si tu veux construire un objet algébrique qui puisse nativement être utilisé au sein d'équations, tu vas commencer à redéfinir les quatre opérations de base (+, -, × et ÷). Avec les trois premières, c'est facile :

    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
        double operator + (double);
        double operator - (double);
        double operator * (double);
    Tant que tu travailles avec des nombres réels, ces opérations restent des lois de composition interne et elles ne peuvent jamais échouer. Tout réel combiné à un autre réel engendra à nouveau un réel et la valeur renvoyée sera toujours valide (nonobstant les inévitables débordements, que l'on peut laisser se produire et que l'on considère comme des UB). Mais en ce qui concerne la division :

    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
        double operator / (double);

    … le même principe reste vrai sauf avec zéro. Le cas de l'opérande nul est l'exception au cas général.

    Comment alors traiter cette situation proprement ?

    On avait déjà le même problème, en langage C, avec fgetc(), par exemple. Cette fonction sert à lire un caractère depuis un flux ou un fichier et, par conséquent, toutes les valeurs couvertes par le type char sont légitimes. Elle renvoie donc un int à la place, qui a le mérite de pouvoir facilement être rétro-transtypé en char et comme le domaine est plus grand par nature, cela dégage de la place pour coder de nouvelles valeurs spéciales mappées ensuite par la macro EOF, par exemple, ce qui permet de vérifier relativement facilement si le code que l'on a reçu est un caractère valide ou une notification de la fonction.

    On est d'accord que si cette approche est la « moins sale » possible, elle reste très loin d'être propre. Et elle a par ailleurs un inconvénient important : elle corrompt la signature de la fonction. Si, dans notre cas, la division renvoyait long double plutôt que double, on ne saurait pas si c'est parce que le résultat doit nécessairement se trouver dans un domaine plus large (comme la division de deux entiers sera garantie d'être réelle, mais pas forcément entière) ou si c'est pour des raisons purement techniques.

    Par ailleurs, si on peut faire facilement un contrôle avec un « if » (ou autre) sur le résultat d'une fonction isolée, on s'imagine mal le faire directement au milieu d'une formule mathématique complexe là où les opérateurs sont utilisés. En outre, il faut tenir compte d'une subtilité supplémentaire : la promotion des types. Lorsque l'on effectue une opérateur telle que a + b en C et C++, le compilateur va choisir le type le plus permissif utilisé par les deux opérandes (quitte à convertir l'autre si nécessaire). C'est pour cela que la division de deux entiers avec a / b est toujours entier lui aussi, ce qui cause des surprises quand on débute et que l'on utilise des fractions. Dans notre cas, en revanche, cela aurait un effet délétère : promouvoir à terme l'intégralité de l'expression au type long double, voire plus large.

    Enfin, de la même façon que l'on a séparé le HTML de sa CSS (en dépréciant tous les attributs de mise en forme), on a souhaité séparer la gestion algorithmique d'une procédure de celle de ses erreurs. Tout ceci a nécessairement conduit à mettre en place des dispositifs dédiés à cela. Donc, étant donné que de toutes façons la division n'est pas définie sur zéro et que l'on ne devrait jamais se trouver dans cette situation au départ, on va lever l'exception si on s'y retrouve quand même.

    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    double operator / (double x) {
     
        if (!x) throw "DivisionByZero";
     
        return this->x / x;
    }

    L'intérêt ici est triple : on conserve une signature propre (on sait que la division reste une opération de type double), une fois écarté le cas de l'opérande nul, on sait qu'on est forcément dans les clous et on peut renvoyer directement le résultat du calcul (mais ça c'est également valable sans recourir aux exceptions en particulier) et surtout, c'est utilisable au sein d'une formule :

    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    MySmartDouble x(5);
    double        r,y = 42;
    
    r = x / (6*7-y)

    … ici, le sous-terme entre parenthèses étant nul une fois les calculs effectués à l'intérieur, l'exception sera levée au niveau de l'opérateur « / ». Sans ce dispositif, il aurait été très difficile d'aller y faire un contrôle explicite.

    En ce sens, ce n'est pas très différent d'une assertion (qui déclenche également un abort() si elle n'est pas vérifiée) mais les notions ne sont pas tout-à-fait les mêmes : les assertions sont essentiellement considérées comme des outils de débogage, qui peuvent être inhibées (passer la macro NDEBUG permet des les définir comme des blocs nuls et donc faire en sorte qu'elles n'apparaissent plus du tout dans un exécutable en production) et qui servent surtout à placer des barrières à intervalles spécifiques dans le code pour s'assurer que certaines conditions censées être certaines à un stade de l'avancement du programme soient bien vérifiées même si le cheminement pour y arriver n'est pas déterministe. Par exemple, s'assurer que tous les fichiers ouverts soient bien refermés quand on a refermé la dernière fenêtre. Si c'est censé être le cas et que c'est nécessaire pour continuer, l'assertion est un contrôle de routine qui permet de s'assurer que le travail a été fait correctement avant de poursuivre.

    Les exceptions, elles, sont vraiment là pour traiter les cas extérieurs au cas général mais dont on sait qu'ils existent.

    Par contre, il faut résister à la tentation de tomber dans l'extrême inverse : s'en servir pour traiter systématiquement les erreurs qui doivent se produire en temps normal, voire les conditions d'arrêt. Par exemple, il est très tentant de lancer une exception pour indiquer une fin de fichier EOF et certains frameworks le font, mais ça reste un défaut de conception dans le sens où atteindre la fin d'un fichier est une situation normale et attendue.


    @fred1599, je n'ai pas vraiment compris le std::optional, quant au pointeur intelligent, je vais déjà commencer par maîtriser le pointeur basique
    Formellement, c'est une classe générique qui va te permettre de décorer un type et le dérivant et en lui ajoutant des propriétés permettant de savoir si la valeur instanciée existe réellement ou non. En fait, cela sert surtout à faire ce que l'on fait avec null dans les langages plus faiblement typés, notamment les langages interprétés comme le Javascript ou le Python, mais également en SQL.

    Lorsque le langage utilise un type-racine, celui-ci peut généralement se décliner en une poignée de sous-types natifs, donc en général chaîne de caractère, nombre réel, tableau, dictionnaire, etc… et « null ». À partir d'eux, on peut faire tout le reste. Il est alors d'usage d'utiliser « null » pour indiquer que la valeur n'existe pas, qu'elle est sans objet dans le contexte, qu'elle n'est pas connue, etc. et ce parce que le marqueur null fait partie du domaine général du type-racine, donc peut le renvoyer directement à la place de la valeur attendue.

    Quand on travaille à plus bas niveau, il faut respecter le type déclaré. Il faut donc utiliser d'emblée un type qui couvre tous les cas de figure possibles, ce qui rejoint la problématique traitée précédemment. Donc, de là, en C, on peut embarquer tout cela dans une structure POD qui contienne à la fois la valeur à renvoyer ET un flag qui indique si oui ou non elle est valide (mais c'est un peu fastidieux) soit, en C++, on dérive le type qui reste utilisable là où il est attendu et on lui ajoute des propriétés, notamment l'opérateur booléen pour l'évaluer directement avec !.

    Comme c'est un cas de figure courant et identifié, la bibliothèque standard nous met à disposition une ressource officielle pour le faire.

  12. #12
    Membre confirmé

    Inscrit en
    Octobre 2003
    Messages
    209
    Détails du profil
    Informations forums :
    Inscription : Octobre 2003
    Messages : 209
    Par défaut
    Franchement, j'ai l'impression d'avoir droit à un cours patriculier, et avec plusieurs profs encore !
    Des réponses denses mais enrichissantes qui me permettent de progresser
    Il faut que j'en décortique certaines pour bien comprendre tous vos apports mais en tout cas, je vois maintenant comment traiter mon problème.
    Encore merci à tous.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Jointure qui ne renvoie pas tous les enregistrements
    Par rayonx dans le forum Langage SQL
    Réponses: 12
    Dernier message: 19/07/2024, 09h33
  2. Réponses: 13
    Dernier message: 09/01/2011, 23h33
  3. constructeur surchargée qui ne fonctionne pas
    Par kate59 dans le forum XNA/Monogame
    Réponses: 1
    Dernier message: 13/06/2008, 22h30
  4. [VB6] générer un recordset qui n'est pas lier à un bdd
    Par damyrid dans le forum VB 6 et antérieur
    Réponses: 3
    Dernier message: 05/06/2003, 17h48
  5. Réponses: 9
    Dernier message: 07/05/2003, 12h57

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