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++Builder Discussion :

Méthodes Virtuelles appelés depuis un Constructeur [Langage/Algorithme]


Sujet :

C++Builder

  1. #1
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 459
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 459
    Points : 24 873
    Points
    24 873
    Par défaut Méthodes Virtuelles appelés depuis un Constructeur
    Avant de lire ce sujet, veillez à lire et relire la FAQ :
    Puis-je appeler des fonctions virtuelles dans le constructeur (ou le destructeur) ?
    Puis la Doc Embarcadero (qui dit le contraire pour la VCL) :
    Appels de méthodes virtuelles dans les constructeurs des classes de base

    Il faudrait notifer dans la FAQ d'une telle différence de comportement, je suis développeur Delphi, je m'attendais au comportement des objets VCL alors que partout sur les sites C++, c'est évidemment le comportement C++ que les développeurs s'attendent !
    J'ai donc ou des sérieux doutes sur certains de mes codes que je n'aurais pas du avoir !

    Si l'on trouve rapidement cet article dans l'aide C++ Builder 6, c'est un défi de la trouver en BSD2007 !

    Un petit code pour illustrer :
    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
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    //---------------------------------------------------------------------------
    #include <typeinfo>
    //---------------------------------------------------------------------------
    class CClassOne
    {
      protected:
        // Methodes Protégées
        virtual void VirtualMethodInConstructor() {OutputDebugString(typeid(this).name()); OutputDebugString("One");}
     
      public:
        /*constructor*/ CClassOne() {VirtualMethodInConstructor();}
    };
    //---------------------------------------------------------------------------
    class CClassTwo : public CClassOne
    {
      protected:
        // Methodes Protégées
        virtual void VirtualMethodInConstructor() {CClassOne::VirtualMethodInConstructor(); OutputDebugString("Two");}
     
      public:
        /*constructor*/ CClassTwo() {}
    };
    //---------------------------------------------------------------------------
    class CClassThree : public CClassTwo
    {
      protected:
        // Methodes Protégées
        virtual void VirtualMethodInConstructor() {CClassTwo::VirtualMethodInConstructor(); OutputDebugString("Three");}
     
      public:
        /*constructor*/ CClassThree() {}
    };
     
    //---------------------------------------------------------------------------
    class TClassOne : public TObject
    {
      protected:
        // Methodes Protégées
        virtual void VirtualMethodInConstructor() {OutputDebugString(typeid(this).name()); OutputDebugString(String(this->ClassName()).c_str()); OutputDebugString("VCL One");}
     
      public:
        /*constructor*/ TClassOne() {VirtualMethodInConstructor();}
    };
    //---------------------------------------------------------------------------
    class TClassTwo : public TClassOne
    {
      protected:
        // Methodes Protégées
        virtual void VirtualMethodInConstructor() {TClassOne::VirtualMethodInConstructor(); OutputDebugString("VCL Two");}
     
      public:
        /*constructor*/ TClassTwo() {}
    };
    //---------------------------------------------------------------------------
    class TClassThree : public TClassTwo
    {
      protected:
        // Methodes Protégées
        virtual void VirtualMethodInConstructor() {TClassTwo::VirtualMethodInConstructor(); OutputDebugString("VCL Three");}
     
      public:
        /*constructor*/ TClassThree() {}
    };
     
     
    void __fastcall TLanguageBasicsForm::BtnConstructorAndVirtualMethodClick(
          TObject *Sender)
    {
      OutputDebugString("! - new CClassOne : ");
      CClassOne *OneC = new CClassOne(); // Affiche CClassOne One
     
      OutputDebugString("! - new CClassTwo : ");
      CClassOne *TwoC = new CClassTwo(); // Affiche CClassOne One
     
      OutputDebugString("! - new CClassThree : ");
      CClassOne *ThreeC = new CClassThree(); // Affiche CClassOne One
     
      OutputDebugString("! - new CClassTwo : ");
      CClassTwo *TwoC2 = new CClassTwo(); // Affiche CClassOne One
     
      OutputDebugString("! - new CClassThree : ");
      CClassTwo *ThreeC2 = new CClassThree(); // Affiche CClassOne One
     
      OutputDebugString("! - new CClassThree : ");
      CClassThree *ThreeC3 = new CClassThree(); // Affiche CClassOne One
     
     
      delete OneC;
      delete TwoC;
      delete ThreeC;
      delete TwoC2;
      delete ThreeC2;
      delete ThreeC3;
     
      OutputDebugString("! - VCL new CClassOne : ");
      TClassOne *OneVCL = new TClassOne(); // Affiche TClassOne TClassOne VCL One
     
      OutputDebugString("! - VCL new TClassTwo : ");
      TClassOne *TwoVCL = new TClassTwo(); // Affiche TClassOne TClassTwo VCL One VCL Two
     
      OutputDebugString("! - VCL new TClassThree : ");
      TClassOne *ThreeVCL = new TClassThree(); // Affiche TClassOne TClassThree VCL One VCL Two VCL Three
     
      OutputDebugString("! - VCL new TClassTwo : ");
      TClassTwo *TwoVCL2 = new TClassTwo(); // Affiche TClassOne TClassTwo VCL One VCL Two
     
      OutputDebugString("! - VCL new TClassThree : ");
      TClassTwo *ThreeVCL2 = new TClassThree(); // Affiche TClassOne TClassThree VCL One VCL Two VCL Three
     
      OutputDebugString("! - VCL new TClassThree : ");
      TClassThree *ThreeVCL3 = new TClassThree(); // Affiche TClassOne TClassThree VCL One VCL Two VCL Three
     
      delete OneVCL;
      delete TwoVCL;
      delete ThreeVCL;
      delete TwoVCL2;
      delete ThreeVCL2;
      delete ThreeVCL3;
    }
    //---------------------------------------------------------------------------
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

  2. #2
    Membre à l'essai
    Profil pro
    Inscrit en
    Janvier 2010
    Messages
    53
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2010
    Messages : 53
    Points : 24
    Points
    24
    Par défaut
    C'est en lisant ce poste que je me rends compte à quel point je suis à la rue. Je n'ai malheureusement pas le temps d'approfondir toutes ces notions.

    L'avantage par contre est de m'avoir fait lire un peu de la FAQ C++ qui m'a apportée beaucoup de réponses!

  3. #3
    Membre averti

    Profil pro
    Inscrit en
    Janvier 2003
    Messages
    288
    Détails du profil
    Informations personnelles :
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations forums :
    Inscription : Janvier 2003
    Messages : 288
    Points : 334
    Points
    334
    Par défaut
    Rassure-toi moi également.
    En fait je ne suis pas tant choqué que ça par la différence de comportement (très étonnant en effet - merci ShaiLeTroll) que par le code suivant:
    Dans quel cas peut-on avoir besoin d'écrire ça ? Pour l'instant c'est ça qui m'interpelle le plus
    B* b = new B(); avec downcast ou upcast, ça ok. Mais le code ci-dessus je ne vois pas de situation ou j'aurais eu besoin (et encore moins envie) d'écrire ça - je ne savais même pas qu'on pouvait le faire.

  4. #4
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Salut,
    Citation Envoyé par ShaiLeTroll Voir le message
    Il faudrait notifer dans la FAQ d'une telle différence de comportement
    C'est une F.A.Q C++ pas BCB. Donc c'est normal que ce ne soit pas mentionné puisque Borland a choisi d'avoir des comportements spécifiques. Préciser les comportements particuliers de chaque framework/compilateur dans la F.A.Q C++ deviendrait vite fastidieux et introduirait probablement de la confusion chez les développeurs C++.

    Ceci dit, ça pourrait effectivement faire partie d'une entrée dédiée dans la FAQ C++ Builder

    Citation Envoyé par yarp Voir le message
    C'est assez classique quand B hérite de A et que l'on souhaite uniquement avoir un pointeur sur la classe de base.

    Autre chose qui m'a surpris dans l'exemple du lien embarcadero proposé par shailLeTroll :
    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
    18
    19
    20
    21
    class  Base :  public  TObject { 
    
    public : 
    __fastcall  Base() { init();  } 
    virtual void __fastcall  init() {  } 
    
    }; 
    
    class  Derived :  public  Base { 
    
    public : 
    Derived( int  nz) : not_zero(nz) {  } 
    virtual void __fastcall  init( ) 
    { 
    
    if  (not_zero == 0 )  // ?????????
    throw  Exception("not_zero is zero!") ; 
    } 
    private : 
    int  not_zero ; 
    } ;
    En C++, la variable ne vaut pas 0 mais n'importe quoi tant qu'elle n'a pas été intialisée

  5. #5
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 459
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 459
    Points : 24 873
    Points
    24 873
    Par défaut
    Oui, je parlais bien d'ajout dans la FAQ C++ Builder !
    Rien que pour cet information ait plus de visibilité !

    Pour les Zéros, voir mon sujet Initialisation à Zéro d'Objet C++, je dirais que c'est même l'une des 1ere chose que j'ai testé !
    Etant programmeur Delphi, c'est le fait qu'un objet C++ Strict ne le fasse pas !
    Du coup, je dérive tout du TObject, rien que parce que j'utilise bcp de TObjectList, les RTTI et que mon code est clairement orienté VCL (quelques exceptions pour vector<int> ou map<String, String>)

    Citation Envoyé par yarp Voir le message
    Dans quel cas peut-on avoir besoin d'écrire ça ? Pour l'instant c'est ça qui m'interpelle le plus
    B* b = new B(); avec downcast ou upcast, ça ok. Mais le code ci-dessus je ne vois pas de situation ou j'aurais eu besoin (et encore moins envie) d'écrire ça - je ne savais même pas qu'on pouvait le faire.
    Ce qui me choque c'est que cela t'interpelle, regarde la TActionList, par défaut, l'item est un TContainedAction avec une instanciation des items par TAction pour les actions génériques, mais il y a des actions prédéfinies comme TWindowClose, TWindowCascade, TFileOpen... c'est un des fondements de la programmation objet : le Polymorphisme !
    Cela se retrouve souvent avec un tableau d'objet comme "Forme[] tableau" de Wikipedia ou avec TObjectList\TCollection

    [MODE TROLL ON]si tu es un professionnel, Change de métier [MODE TROLL OFF]
    si tu es un amateur, voilà une grande découverte pour toi !
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

  6. #6
    Membre chevronné Avatar de nirgal76
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Septembre 2007
    Messages
    905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Autre

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

    Informations forums :
    Inscription : Septembre 2007
    Messages : 905
    Points : 2 127
    Points
    2 127
    Par défaut
    Citation Envoyé par 3DArchi Voir le message
    Salut,
    C'est une F.A.Q C++ pas BCB. Donc c'est normal que ce ne soit pas mentionné puisque Borland a choisi d'avoir des comportements spécifiques. Préciser les comportements particuliers de chaque framework/compilateur dans la F.A.Q C++ deviendrait vite fastidieux et introduirait probablement de la confusion chez les développeurs C++.

    Ceci dit, ça pourrait effectivement faire partie d'une entrée dédiée dans la FAQ C++ Builder

    C'est assez classique quand B hérite de A et que l'on souhaite uniquement avoir un pointeur sur la classe de base.

    Autre chose qui m'a surpris dans l'exemple du lien embarcadero proposé par shailLeTroll :
    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
    18
    19
    20
    21
    class  Base :  public  TObject { 
    
    public : 
    __fastcall  Base() { init();  } 
    virtual void __fastcall  init() {  } 
    
    }; 
    
    class  Derived :  public  Base { 
    
    public : 
    Derived( int  nz) : not_zero(nz) {  } 
    virtual void __fastcall  init( ) 
    { 
    
    if  (not_zero == 0 )  // ?????????
    throw  Exception("not_zero is zero!") ; 
    } 
    private : 
    int  not_zero ; 
    } ;
    En C++, la variable ne vaut pas 0 mais n'importe quoi tant qu'elle n'a pas été intialisée
    Oui mais la mémoire des classes vcl est initialisé à zéro. Pour des variables membres comme not_zero, ça signifie que sa valeur sera 0 à l'initialisation. Ce qui n'est pas valable en C++ standard ou sa valeur sera effectivement indéterminée.
    Donc danger, ça fait prendre de mauvaises habitudes et peut rendre le portage du code vers d'autres C++ très aléatoire en terme de stabilité.
    Et d'une manière général, j'évite tout code qui peut déclencher une exception dans un constructeur. J'appelle la fonction Init() après construction dont le rôle se limite à construire l'objet.

  7. #7
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Citation Envoyé par nirgal76 Voir le message
    Oui mais la mémoire des classes vcl est initialisé à zéro. Pour des variables membres comme not_zero, ça signifie que sa valeur sera 0 à l'initialisation. Ce qui n'est pas valable en C++ standard ou sa valeur sera effectivement indéterminée.
    Donc danger, ça fait prendre de mauvaises habitudes et peut rendre le portage du code vers d'autres C++ très aléatoire en terme de stabilité.
    J'aurais tendance à faire comme toi, privilégier les codes portables, mais je ne sais pas quels sont les habitudes des projets BCB.
    Citation Envoyé par nirgal76 Voir le message
    Et d'une manière général, j'évite tout code qui peut déclencher une exception dans un constructeur. J'appelle la fonction Init() après construction dont le rôle se limite à construire l'objet.
    Ben, là je ne suis pas d'accord en C++ standard. Le constructeur est fait pour produire un objet directement utilisable (i.e. tous les invariants ont été construits/mis en place). S'il ne peut pas, alors génération d'une exception. Le langage sait quels destructeurs appeler ou pas dans ce cas. Séparer en constructeur + fonction ini c'est perdre l'intérêt du constructeur + celui des exceptions en refusant de les lancer dans le constructeur. Les exceptions dans les constructeurs, c'est sans risque si on a bien fait attention à gérer ses ressources avec des enveloppes RAII. L'utilisation de constructeur + ini c'est l'assurance d'avoir un jour ou l'autre une situation où on 'oublie' d'appeler la fonction d'ini. Et là, en général boum

    Là où on ne lance pas d'exception, c'est dans un destructeur. Ca a peu de sens de dire qu'une destruction échoue. Ca peut même être assez problématique dans certains cas.

    Cf F.A.Q (C++) :
    Peut-on lever des exceptions dans les constructeurs ?
    Peut-on lever des exceptions dans les destructeurs ?

  8. #8
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 459
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 459
    Points : 24 873
    Points
    24 873
    Par défaut
    En fait, en C++, il y a une initialisation des objets statiques !
    Il ne faut pas l'oublier, des objets tel que vector ou String !

    le seul moyen pour interrompre une construction, c'est une exception, en Delphi, il faut savoir que si une exception se produit durant le constructeur, cela appelle le destructeur (d'où l'utilité de l'initialisation à zéro pour tester nil\null)
    En C++, faudra que je teste, ce comportement, il me semble qu'il faut soit même gérer les libérations en cas d'exception durant le constructeur !
    Encore une fois les différences C++ et VCL à ce sujet : Destruction d'objets qu'il faut comparer à la FAQ Peut-on lever des exceptions dans les constructeurs ?

    Il suffit de faire deux méthodes InternalCreate et InternalDestroy, le destroy étant capable de libérer un objet complètement ou partiellement construit !

    Pour le portage vers d'autres C++, cela me fait toujours sourire quand l'on utilise C++ Builder et de vouloir faire un code compatible et portable !
    Trop de directive, de mot clé, ... mieux vaut écrire un C++ avec gcc, puis de l'utiliser en BCB, mais faire l'inverse, ça semble pénible de se limiter au C++ strict (tout simplement parce que l'IDE n'est pas conçu pour cela, on peut s'en sortir en faisant une DLL en désactivant le support VCL)

    En plus, mieux vaut éviter, plus il y a de besoins pour écrire un code C++ spécifique à chaque environnement, plus cela fait d'emploi !
    Les grosses sociétés ne se posent pas la question : "faisons un code portable", lorsqu'il y a déjà 30 développeurs Delphi, 40 développeur .NET, 20 intégrateurs SAP, embauche 10 de plus via des SSII pour faire du VSC++ et 10 autres pour du C++Builder, ils embauchent puis jette le développeur comme vieille chaussette trouée ! Je connais au moins 4 développeur qui ont vécu cela !

    Tu appeles Init après le constructeur ! en dehors !
    J'espère que le code est bien documenté pour que les autres développeurs qui travaille avec tes objets ne l'oublie pas !

    J'en reviens sur les méthodes virtuelles appelées depuis un constructeur qui contourne en VCL, la sécurité naturelle du C++
    Pourquoi cette règle ? Une fonction définie dans C a accès aux données membre de C. Or, on a vu que au moment où on exécute l'appel au corps du constructeur de B, ces dernières ne sont pas encore créées. On a donc préféré jouer la sécurité.
    Ce qui suit est assez hallucinant aussi, j'ai commencé le développement d'une couche Objet Métier générique qui utilise les RTTI pour s'alimenter à partir d'une DB
    L'initialisation des objets C++ par "itération" peut provoquer ce genre d'anomalie si l'on joue avec les méthodes virtuelles depuis un ancêtre ce que fait la TForm en lisant la DFM, vous remarquerez que les membres publiés d'une TForm soit des Pointeurs sur Objet qui ne sont pas initialisé par les mécanismes C++ ou des property (qui ne sont pas affecté non plus)

    Du coup, j'utilise des String* avec des accesseurs pour contourner l'initialisation automatique des objets statiques par "itération" du C++

    Vous noterez pour TClassTwo
    que l'on pert Two et mais pas 2 lors de l'initialisation de TClassTwo

    Vous noterez pour TClassThree
    que l'on pert Two et mais pas 2 ni Three, ni 3 lors de l'initialisation de TClassTwo
    que l'on fini par perdre Three mais ni 2 ni 3 lors de l'initialisation de TClassThree


    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
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    //---------------------------------------------------------------------------
    #include <typeinfo>
    //---------------------------------------------------------------------------
    class CClassOne
    {
      protected:
        // Methodes Protégées
        virtual void VirtualMethodInConstructor() {OutputDebugString(typeid(this).name()); OutputDebugString("One");}
     
      public:
        /*constructor*/ CClassOne() {VirtualMethodInConstructor();}
    };
    //---------------------------------------------------------------------------
    class CClassTwo : public CClassOne
    {
      protected:
        // Methodes Protégées
        virtual void VirtualMethodInConstructor() {CClassOne::VirtualMethodInConstructor(); OutputDebugString("Two");}
     
      public:
        /*constructor*/ CClassTwo() {}
    };
    //---------------------------------------------------------------------------
    class CClassThree : public CClassTwo
    {
      protected:
        // Methodes Protégées
        virtual void VirtualMethodInConstructor() {CClassTwo::VirtualMethodInConstructor(); OutputDebugString("Three");}
     
      public:
        /*constructor*/ CClassThree() {}
    };
     
    //---------------------------------------------------------------------------
    class TClassOne : public TObject
    {
      private:
        // Membres Privés
        int FValueOne;
        String FTextOne;
     
      protected:
        // Methodes Protégées
        virtual void VirtualMethodInConstructor() {OutputDebugString(typeid(this).name()); OutputDebugString(String(this->ClassName()).c_str()); OutputDebugString("VCL One");}
     
      public:
        /*constructor*/ TClassOne();
     
      __published:
        // Propriétés Publiées
        __property int ValueOne = {read=FValueOne, write=FValueOne};
        __property String TextOne = {read=FTextOne, write=FTextOne};
    };
    //---------------------------------------------------------------------------
    TClassOne:: TClassOne()
    {
      VirtualMethodInConstructor();
     
      if (IsPublishedProp(this, "ValueOne")) SetOrdProp(this, "ValueOne", 1);
      if (IsPublishedProp(this, "ValueTwo")) SetOrdProp(this, "ValueTwo", 2);
      if (IsPublishedProp(this, "ValueThree")) SetOrdProp(this, "ValueThree", 3);
      if (IsPublishedProp(this, "TextOne")) SetStrProp(this, "TextOne", "One");
      if (IsPublishedProp(this, "TextTwo")) SetStrProp(this, "TextTwo", "Two");
      if (IsPublishedProp(this, "TextThree")) SetStrProp(this, "TextThree", "Three");
      String ODSText = "Published One : ";
      if (IsPublishedProp(this, "ValueOne")) ODSText += "ValueOne : \"" + IntToStr(GetOrdProp(this, "ValueOne")) + "\" - ";
      if (IsPublishedProp(this, "ValueTwo")) ODSText += "ValueTwo : \"" + IntToStr(GetOrdProp(this, "ValueTwo")) + "\" - ";
      if (IsPublishedProp(this, "ValueThree")) ODSText += "ValueThree : \"" + IntToStr(GetOrdProp(this, "ValueThree")) + "\" - ";
      if (IsPublishedProp(this, "TextOne")) ODSText += "TextOne : \"" + GetStrProp(this, "TextOne") + "\" - ";
      if (IsPublishedProp(this, "TextTwo")) ODSText += "TextTwo : \"" + GetStrProp(this, "TextTwo") + "\" - ";
      if (IsPublishedProp(this, "TextThree")) ODSText += "TextThree : \"" + GetStrProp(this, "TextThree") + "\" - ";
      OutputDebugString(ODSText.c_str());
    }
     
    //---------------------------------------------------------------------------
    class TClassTwo : public TClassOne
    {
      private:
        // Membres Privés
        int FValueTwo;
        String FTextTwo;
     
      protected:
        // Methodes Protégées
        virtual void VirtualMethodInConstructor() {TClassOne::VirtualMethodInConstructor(); OutputDebugString("VCL Two");}
     
      public:
        /*constructor*/ TClassTwo();
     
      __published:
        // Propriétés Publiées
        __property int ValueTwo = {read=FValueTwo, write=FValueTwo};
        __property String TextTwo = {read=FTextTwo, write=FTextTwo};
    };
    //---------------------------------------------------------------------------
    TClassTwo:: TClassTwo()
    {
      String ODSText = "Published Two : ";
      if (IsPublishedProp(this, "ValueOne")) ODSText += "ValueOne : \"" + IntToStr(GetOrdProp(this, "ValueOne")) + "\" - ";
      if (IsPublishedProp(this, "ValueTwo")) ODSText += "ValueTwo : \"" + IntToStr(GetOrdProp(this, "ValueTwo")) + "\" - ";
      if (IsPublishedProp(this, "ValueThree")) ODSText += "ValueThree : \"" + IntToStr(GetOrdProp(this, "ValueThree")) + "\" - ";
      if (IsPublishedProp(this, "TextOne")) ODSText += "TextOne : \"" + GetStrProp(this, "TextOne") + "\" - ";
      if (IsPublishedProp(this, "TextTwo")) ODSText += "TextTwo : \"" + GetStrProp(this, "TextTwo") + "\" - ";
      if (IsPublishedProp(this, "TextThree")) ODSText += "TextThree : \"" + GetStrProp(this, "TextThree") + "\" - ";
      OutputDebugString(ODSText.c_str());
    }
    //---------------------------------------------------------------------------
    class TClassThree : public TClassTwo
    {
      private:
        // Membres Privés
        int FValueThree;
        String FTextThree;
     
      protected:
        // Methodes Protégées
        virtual void VirtualMethodInConstructor() {TClassTwo::VirtualMethodInConstructor(); OutputDebugString("VCL Three");}
     
      public:
        /*constructor*/ TClassThree();
     
      __published:
        // Propriétés Publiées
        __property int ValueThree = {read=FValueThree, write=FValueThree};
        __property String TextThree = {read=FTextThree, write=FTextThree};
    };
    //---------------------------------------------------------------------------
    TClassThree:: TClassThree()
    {
      String ODSText = "Published Three : ";
      if (IsPublishedProp(this, "ValueOne")) ODSText += "ValueOne : \"" + IntToStr(GetOrdProp(this, "ValueOne")) + "\" - ";
      if (IsPublishedProp(this, "ValueTwo")) ODSText += "ValueTwo : \"" + IntToStr(GetOrdProp(this, "ValueTwo")) + "\" - ";
      if (IsPublishedProp(this, "ValueThree")) ODSText += "ValueThree : \"" + IntToStr(GetOrdProp(this, "ValueThree")) + "\" - ";
      if (IsPublishedProp(this, "TextOne")) ODSText += "TextOne : \"" + GetStrProp(this, "TextOne") + "\" - ";
      if (IsPublishedProp(this, "TextTwo")) ODSText += "TextTwo : \"" + GetStrProp(this, "TextTwo") + "\" - ";
      if (IsPublishedProp(this, "TextThree")) ODSText += "TextThree : \"" + GetStrProp(this, "TextThree") + "\" - ";
      OutputDebugString(ODSText.c_str());
    }
     
     
    void __fastcall TLanguageBasicsForm::BtnConstructorAndVirtualMethodClick(
          TObject *Sender)
    {
      OutputDebugString("! - new CClassOne : ");
      CClassOne *OneC = new CClassOne(); // Affiche CClassOne One
     
      OutputDebugString("! - new CClassTwo : ");
      CClassOne *TwoC = new CClassTwo(); // Affiche CClassOne One
     
      OutputDebugString("! - new CClassThree : ");
      CClassOne *ThreeC = new CClassThree(); // Affiche CClassOne One
     
      OutputDebugString("! - new CClassTwo : ");
      CClassTwo *TwoC2 = new CClassTwo(); // Affiche CClassOne One
     
      OutputDebugString("! - new CClassThree : ");
      CClassTwo *ThreeC2 = new CClassThree(); // Affiche CClassOne One
     
      OutputDebugString("! - new CClassThree : ");
      CClassThree *ThreeC3 = new CClassThree(); // Affiche CClassOne One
     
     
      delete OneC;
      delete TwoC;
      delete ThreeC;
      delete TwoC2;
      delete ThreeC2;
      delete ThreeC3;
     
      OutputDebugString("! - VCL new CClassOne : ");
      TClassOne *OneVCL = new TClassOne(); // Affiche TClassOne TClassOne VCL One
     
      OutputDebugString("! - VCL new TClassTwo : ");
      TClassOne *TwoVCL = new TClassTwo(); // Affiche TClassOne TClassTwo VCL One VCL Two
     
      OutputDebugString("! - VCL new TClassThree : ");
      TClassOne *ThreeVCL = new TClassThree(); // Affiche TClassOne TClassThree VCL One VCL Two VCL Three
     
      OutputDebugString("! - VCL new TClassTwo : ");
      TClassTwo *TwoVCL2 = new TClassTwo(); // Affiche TClassOne TClassTwo VCL One VCL Two
     
      OutputDebugString("! - VCL new TClassThree : ");
      TClassTwo *ThreeVCL2 = new TClassThree(); // Affiche TClassOne TClassThree VCL One VCL Two VCL Three
     
      OutputDebugString("! - VCL new TClassThree : ");
      TClassThree *ThreeVCL3 = new TClassThree(); // Affiche TClassOne TClassThree VCL One VCL Two VCL Three
     
      delete OneVCL;
      delete TwoVCL;
      delete ThreeVCL;
      delete TwoVCL2;
      delete ThreeVCL2;
      delete ThreeVCL3;
    }
    //---------------------------------------------------------------------------
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

  9. #9
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Citation Envoyé par ShaiLeTroll Voir le message
    En fait, en C++, il y a une initialisation des objets statiques !
    je ne sais pas ce que tu entends par là. Mais pour un objet donnée la construction est donnée ici : Dans quel ordre sont construits les différents composants d'une classe ?

    A vrai dire, il y a pas mal de subtilité entre les initialisations par défaut, les initialisations à zero, les non-initialisation, les variables membres, les variables locales à une fonction, les variables globales (statique ou non), les variables statiques d'une classes, etc.

    Donc j'ai du mal à comprendre ta remarque.

    Un exemple bête en C++ :
    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
    18
    19
    20
    21
    22
    23
    24
    25
    26
    #include <iostream>
     
    int glob;
     
    struct A
    {
        int m;
        static int ms;
    };
    int A::ms;
     
    int main()
    {
        int i;
        A a;
        static int j;
     
        std::cout<<glob<<"\n"; // 1
        std::cout<<i<<"\n"; // 2
        std::cout<<j<<"\n"; // 3
        std::cout<<a.m<<"\n"; // 4
        std::cout<<A().m<<"\n"; // 5
        std::cout<<A::ms<<"\n"; // 6
     
        return 0;
    }
    1, 3, 5 et 6 valent 0
    2 et 4 sont indéterminés
    (et on n'a pas encore parlé de l'ordre dans lequel tout ça a lieu )

    Citation Envoyé par ShaiLeTroll Voir le message
    le seul moyen pour interrompre une construction, c'est une exception, en Delphi, il faut savoir que si une exception se produit durant le constructeur, cela appelle le destructeur (d'où l'utilité de l'initialisation à zéro pour tester nil\null)
    En C++, lorsqu'une exception est levée dans un constructeur, les destructeurs exécutés sont ceux dont le constructeur a été entièrement exécuté. Donc, pour une classe C qui lèverait une exception dans son constructeur, les destructeurs appelés seraient ceux de ses membres et de ses classes de base mais pas celui de C :
    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
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    #include <iostream>
     
    struct A
    {
        ~A()
        {
            std::cout<<"A\n";
        }
    };
     
    struct B
    {
        ~B()
        {
            std::cout<<"B\n";
        }
    };
     
    struct C : public A
    {
        C()
        {
            throw 1;
        }
        ~C()
        {
            std::cout<<"C\n";
        }
     
        B b;
    };
     
     
    int main()
    {
        try
        {
            C c;
        }
        catch(int)
        {
     
        }
     
        return 0;
    }
    => produit A, B

    Citation Envoyé par ShaiLeTroll Voir le message
    En C++, faudra que je teste, ce comportement, il me semble qu'il faut soit même gérer les libérations en cas d'exception durant le destructeur !
    RAII et pas de soucis
    (t'as mis durant le destructeur mais j'imagine que tu voulais dire durant le constructeur


    Citation Envoyé par ShaiLeTroll Voir le message

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    //--------------------------------------------------------------------------
    Quelque chose m'a surpris dans ton code : c'est l'appel systématique à la classe de base pour les fonctions virtuelles. Si c'est un vrai besoin pourquoi ne pas faire :
    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
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    class A
    {
    public:
        void fonction()
        {
            prepare_fonction();
            do_fonction();
        }
     
        virtual ~A(){}
    private:
        void prepare_fonction()
        {
            // blablabla
        }
        virtual void do_fonction()
        {
        }
    };
     
    class B : public A
    {
        private:
        virtual void do_function()
        {
            // specific
        }
    };

  10. #10
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 459
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 459
    Points : 24 873
    Points
    24 873
    Par défaut
    Citation Envoyé par 3DArchi Voir le message
    je ne sais pas ce que tu entends par là. Mais pour un objet donnée la construction est donnée ici : Dans quel ordre sont construits les différents composants d'une classe ?
    Effectivement
    En fait, en C++, il y a une initialisation des objets statiques !
    j'aurais du le dire
    En fait, en C++, il y a une initialisation des objets à allocation statique !

    oui, j'ai vu cette FAQ, prendre des struct avec de l'héritage, ça me suprend, mais "MembreA m" c'est une allocation statique (différente des allocations dynamiques avec new)

    je ne parlais des membres de classe marqué par static, des membres d'instance (ce qui l'on utilise généralement) avec une allocation statique, les termes sont maladroits

    Pour la RAII, je l'ai lu, cela suppose de faire des objets C++ strict, ce que je ne fais que très rarement, et mes objets ont une très longue durée de vie (server), et sont souvent dans des listes
    Pour lire un fichier, je vais utiliser un TFileStream*, j'utilise la VCL, développeur Delphi oblige !
    j'utilise même try __finally, j'ai l'habitude de libérer mes objets (mes pointeurs d'objets), cela me dérange pas, c'est un pure reflexe, un new dans un constructeur ou via une lazy initialisation, j'ajoute mon delete dans le destructeur ! je le fais instinctivement !

    Pour l'exception durant le constructeur de C, Delphi appele juste le destructeur de C (et comme le développeur Delphi sait ce qu'il fait, il appelle inherited destroy();, ce qui appelle B puis A puis TObject)
    En Delphi, tu oublies "inherited destroy()", c'est l'erreur garanti, un grand classique chez le débutant !


    t'as mis durant le destructeur mais j'imagine que tu voulais dire durant le constructeur
    Tu as corrigé, une exception dans un destructeur est une abbération !
    j'ai édité !

    Quelque chose m'a surpris dans ton code : c'est l'appel systématique à la classe de base pour les fonctions virtuelles. Si c'est un vrai besoin pourquoi ne pas faire :
    C'est juste pour montrer l'ordre d'execution, c'est un code démonstratif, rien de plus !
    Dans mon vrai code, je ne suis pas allez aussi loin, pour le moment, ma méthode virtuelle héritée n'appele pas la méthode ancêtre tout simplement parce qu'elle remplace son comportement !
    L'appel de la méthode de la classe de base sera en fonction du besoin !
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

  11. #11
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 459
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 459
    Points : 24 873
    Points
    24 873
    Par défaut
    Pour continuer l'appel de Méthodes Virtuelles dans un Constructeur !
    Il y a encore plus spectaculaire !
    L'Appel d'un Constructeur Virtuel dans un Constructeur Vous me dites pas possible en C++ ! Démonstration !

    Comment la VCL et ses comportements Delphiesque peuvent faire devenir fou C++ Builder !

    Un collègue a transformé un constructeur de fenêtre existant
    Code de Départ

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    __fastcall TUneFormMachin::TUneFormMachin(TComponent* AOwner, bool FlagTruc)
      : TForm(AOwner)
    en
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    __fastcall TUneFormMachin::TUneFormMachin(TComponent* AOwner, int StateTruc)
      : TForm(AOwner)
    Il a changé les appels pour gérer plusieurs etats numériques au lieu d'un état vrai\faux Jusqu'à la rien de choquant !
    Rappelons que
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    	/* TCustomForm.Create */ inline __fastcall virtual TForm(Classes::TComponent* AOwner) : TCustomForm(AOwner) { }
    	/* TCustomForm.CreateNew */ inline __fastcall virtual TForm(Classes::TComponent* AOwner, int Dummy) : TCustomForm(AOwner, Dummy) { }
    Mon collègue m'appelle parce qu'il a une violation d'accès, je regarde son code, tout à l'air bon, la DFM nickel, je lui fait lancer pas à pas avec F7, et je constate que cela boucle : recursivité !
    D'ailleurs l'erreur était EStackOverflow : Débordement de Pile
    typique d'une récursivité incontrôlée !

    Je lui fait changé son implémentation de

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    __fastcall TUneFormMachin::TUneFormMachin(TComponent* AOwner, int StateTruc)
      : TForm(AOwner)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    __fastcall TUneFormMachin::TUneFormMachin(TComponent* AOwner, int StateTruc)
      : TForm(AOwner, -1)
    Le -1 force l'appel à CreateNew hérité !
    La Récursivité ne se produit plus (bon cela pose d'autres problèmes puisque que CreateNew ignore la DFM)
    Un début de piste se profil à mes yeux !

    Comme on a pu voir, un objet VCL crée sa VMT dès son instanciation contrairement à un objet C++ strict qui crée sa VMT par "itération" lors de l'appel des constructeurs depuis la base vers sa classe finale

    Dans le Code TForm, on peut voir que l'on a deux constructeurs
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    TForm(TComponent) // Create
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    TForm(TComponent, int) // CreateNew
    Si l'on reprend le constructeur
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    __fastcall TUneFormMachin::TUneFormMachin(TComponent* AOwner, int StateTruc);
    On voit qu'il reprend la forme de CreateNew !
    Nous avons donc un constructeur virtuel, comme la VMT est chargé dès l'instanciation de l'objet, ce constructeur virtuel remplace celui de TCustomForm !

    Etudions du constructeur TForm(TComponent) qui n'est que redéclaration de TCustomForm(TComponent)

    Code pascal : 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
    18
    19
    20
    constructor TCustomForm.Create(AOwner: TComponent);
    begin
      GlobalNameSpace.BeginWrite;
      try
        CreateNew(AOwner);
        if (ClassType <> TForm) and not (csDesigning in ComponentState) then
        begin
          Include(FFormState, fsCreating);
          try
            if not InitInheritedComponent(Self, TForm) then
              raise EResNotFound.CreateFmt(SResNotFound, [ClassName]);
          finally
            Exclude(FFormState, fsCreating);
          end;
          if OldCreateOrder then DoCreate;
        end;
      finally
        GlobalNameSpace.EndWrite;
      end;
    end;

    En C++, rapellons que les Constructeurs ne peuvent pas s'appeler entre-eux et que l'on utilise une méthode commune souvent nommé initialize pour partager des traitements entre plusieurs constructeurs

    En Delphi, un Constructeur A peut appeler un Constructeur B, effectivement inutile d'avoir une méthode commune initialize pour partager des traitements c'est quasiment l'idiome "named constructor" qui est utilisé !

    On note alors que dans le code Delphi de il y a un appel à CreateNew donc Hors nous avions constaté que
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    TUneFormMachin(TComponent, int)
    remplaçait CreateNew !

    Nous voilà en face d'un dilemme, en C++, nativement, le mot clé virtual est automatique pour les méthodes héritées qui surchargent automatiquement la méthode ancêtre !

    En Delphi, c'est différent, par défaut, si l'on crée une méthode virtuelle, pour la surcharger, la classe héritée doit utiliser le mot clé override !
    Sans ce mot clé, la méthode n'est pas surchargé, ainsi le comportement n'est pas virtuel !

    En C++ Builder, il existe un mot clé que je n'aurais jamais pensé utilisé (je l'ai découvert au début de mes recherches sur les méthodes virtuelles en C++, je ne voyais pas dans quel cas, il était nécessaire, en général, j'évite de redéfinir une méthode virtuelle sans la surcharger, je lui change son nom par exemple)
    Ce mot clé est HIDESBASE

    que l'on ajoute dans le Header .h

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    class TUneFormMachin : public TForm
    ...
    HIDESBASE __fastcall TUneFormMachin(TComponent* AOwner, int StateTruc);
    Cela indique que l'on surcharge pas CreateNew mais que l'on ajoute un nouveau constructeur non virtuel à notre classe !

    La Récursivité imprévue disparait, mon collègue était fort content d'avoir une solution à son problème !

    Voilà, HIDESBASE est une façon plus propre de résoudre le sujet StackOverflow constructeur au lieu de l'ajout d'un second "int Dummy" supplémentaire qui est la seule méthode C++ pour redéfinir un constructeur
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

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

Discussions similaires

  1. [PHP 5.3] Savoir quelle méthode est appelée via le constructeur
    Par Madfrix dans le forum Langage
    Réponses: 14
    Dernier message: 19/10/2010, 16h45
  2. Réponses: 6
    Dernier message: 10/10/2007, 20h11
  3. Réponses: 15
    Dernier message: 05/07/2007, 01h29
  4. Appel d'une méthode virtuelles
    Par BIPBIP59 dans le forum C++Builder
    Réponses: 4
    Dernier message: 24/03/2006, 14h00
  5. Comment l'appel à une méthode virtuelle....
    Par Blobette dans le forum C++
    Réponses: 7
    Dernier message: 07/12/2004, 13h55

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