1

Bah oué je m'emmerde toujours, et y'a de vrais trésors dans ces archives.
    #include <iostream>
    #include <complex>
    using namespace std;

    class Base {
    public:
        virtual void f( int ) {
            cout << "Base::f(int)" << endl;
        }

        virtual void f( double ) {
            cout << "Base::f(double)" << endl;
        }

        virtual void g( int i = 10 ) {
            cout << i << endl;
        }
    };

    class Derived: public Base {
    public:
        void f( complex<double> ) {
            cout << "Derived::f(complex)" << endl;
        }

        void g( int i = 20 ) {
            cout << "Derived::g() " << i << endl;
        }
    };

    void main() {
        Base    b;
        Derived d;
        Base*   pb = new Derived;

        b.f(1.0);
        d.f(1.0);
        pb->f(1.0);

        b.g();
        d.g();
        pb->g();

        delete pb;
    }

Question :
1) qu'est-ce que ce code va afficher ?
2) il y a un bug dedans, quel est-il ?

2

1) à vue de nez je dirais
Base::f(double)
Base::f(double)
Base::f(double)
10
Derived::g(20) Derived::g(10)
(mais je me trompe p-ê ^^)

2) qu'il manque un
void f( double d ) {
    f(( complex<double> ) d);
}
dans Derived ?

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

3

1)

Pour la fonction g c'est bien vu smile
Pour la fonction f, nan c'est plus subtil que ça (je l'ai appris dans leur exemple aussi, comme quoi)

Commençons par le 2ème appel (le premier est bon évidememnt) :
=> Il va donner Derived::f(complex)

Alors pourquoi... ben l'explication est toute bête, mais je la connaissais pas jusqu'à il y a peu.

Le 3ème appel...on verra après le second cheeky

2)
non, ce n'est pas nécessaire, et ça ne provoque pas de bug. Il y a un vrai bug susceptible de planter le programme.

4

1) ah oui c'est vrai, définir une fonction dans une classe dérivée masque totalement la fonction de base avec toute ses surcharges... (pfiou, ça fait une éternité que j'ai pas programmé en C++ happy #mac#)
et pour le 3è appel, je vois pas de raison pour laquelle ça serait autre chose que Base::f(double) what

2) main() renvoie void, c'est donc une undefined behaviour tongue dehors

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

5

il manque le .h des #include, c'est sûrement ça
avatar
« Le bonheur, c'est une carte de bibliothèque ! » — The gostak distims the doshes.
Membrane fondatrice de la confrérie des artistes flous.
L'univers est-il un dodécaèdre de Poincaré ?
(``·\ powaaaaaaaaa ! #love#

6

Base::f(double)
Base::f(complex)
Base::f(double)
10
Derived::g(20)
Derived::g(10)

-> complex<double>

EDIT: OUPS en retard

7

Pollux> 1) yep, comme quoi on en réapprend régulièrement
Le troisième appel est bien celui là, mais comme je disais, faut faire dans l'ordre cheeky

2) c'est vrai (d'ailleurs dans l'article ils l'ont noté en commentaire). Mais il y a pire.

Sally> Y'a pas de .h aux includes c++ standard ^^

8

Euh je connais pas le C++, mais b.f(1.0) ça appelle une méthode virtuelle confus
edit : mais en fait c'est une méthode qui est "virtual" mais qui est implémentée quand même, c'est quoi le principe du virtual au juste ??
avatar
« Le bonheur, c'est une carte de bibliothèque ! » — The gostak distims the doshes.
Membrane fondatrice de la confrérie des artistes flous.
L'univers est-il un dodécaèdre de Poincaré ?
(``·\ powaaaaaaaaa ! #love#

9

Les fonctions virtuelles interviennent lors de l'utilisation polymorphique d'un pointeur sur une classe de base.
Elles permettent d'appeler les fonctions redéfinies dans la classe dérivée à partir d'un pointeur sur la classe de base.

class Base {
    void a() {cout <<Base::a() <<endl;}
    virtual void b() {cout <<Base::b() <<endl;}
};

classe Derived : public Base {
    void a() {cout <<Base::a() <<endl;}
    virtual void b() {cout <<Base::b() <<endl;}
};

int main() {
    Base  *X = new Derived;
    X->a();    // Base::a()
    X->b();    // Derived::b()
}

10

je dirais delete (Derived)pb;

11

Sally :
edit : mais en fait c'est une méthode qui est "virtual" mais qui est implémentée quand même, c'est quoi le principe du virtual au juste ??

Les fonctions qui ne sont pas implémentées, c'est les fonctions qui sont à la fois "virtual" *et* abstraites (noté par "= 0;" à la place du corps de la fonction en C++) : ça implique que ta classe de base ne sera pas instantiable (classe abstraite), puisqu'elle n'implémente pas cette méthode...

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

12

./10> Je pense que t'as trouvé le problème. Mais ta solution n'est pas bonne.

13

delete (Derived *)pb; happy Mais pourquoi ? confus Il faut impérativement que le destructeur soit virtuel, même s'il est trivial ?

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

14

Oui. Ca n'empèche pas la compilation s'il ne l'est pas, mais ça fait de vilains bugs. Par exemple =>

class Base {
};

class Derivated {
~Derivated() {cout <<"test" <<endl;}
};

int main() {
    Base * X = new Derivated;
    delete X;
}

Rien ne sera affiché ^^
C'est très genant si tu fais des choses importantes dans le destructeur de Derivated
Et comme le compilateur peut très bien avoir du code à lui important à la fin du destructeur de Derivated, tu risques le plantage même si c'est un destructeur trivial (donc même s'il n'y a pas de destructeur marqué du tout, le compilateur générant dans ce cas un destructeur vide).

C'est pour ça que dans toute classe destinée à être dérivable, le destructeur doit être virtuel.

15

Ou lala faut que je me cache vu la reponse que j ai donnée sad

16

spectras
: C'est très genant si tu fais des choses importantes dans le destructeur de Derivated

Ca je m'en doute bien cheeky
Et comme le compilateur peut très bien avoir du code à lui important à la fin du destructeur de Derivated, tu risques le plantage même si c'est un destructeur trivial (donc même s'il n'y a pas de destructeur marqué du tout, le compilateur générant dans ce cas un destructeur vide).

Tu es vraiment sûr de ce que tu dis ? (je ne dis pas que tu as tort, juste que c'est étonnant) Tu sais si c'est marqué dans le standard, ou tu as un exemple de cas concret où GCC ferait n'importe quoi ?

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

17

J'ai pas de quoi compiler sous la main, j'essaierai ce soir ^^
Cela dit, je regarderais bien du côté des variables membre de type classe (avec un vrai destructeur non trivial)

18

Ben oui, mais une classe dérivée ne peut qu'avoir des membres *en plus*... Au pire ils ne seront pas détruits, mais si justement on sait qu'ils n'ont pas de destructeurs non-triviaux, ça devrait être possible d'avoir un destructeur non virtuel, non ?

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

19

Ca dépend du compilateur après. Qui dit par exemple, que la gestion des RTTI n'a pas besoin de libérer des trucs, ou la gestion des fonctions virtuelles, ou pourquoi pas des exceptions ? Ou encore un mode debugging qui injecterais du code stockant des infos sur la durée de vie de l'objet dans une zone allouée dynamiquement ?
Ce n'est pas interdit, et le compilo est en droit de le faire.

En plus, le simple fait de "savoir qu'ils n'ont pas de destructeurs non triviaux" rompt le modèle d'encapsulation (on devient dépendant de l'implémentation des objets membre et non plus simplement de leur interface).

20

spectras
: Ca dépend du compilateur après.

Oui enfin là je parle du compilateur le plus "méchant" permis par le standard... Est-ce qu'il a vraiment le droit de faire tout ce que tu racontes ? confus
Si je fais mon propre opérateur new qui alloue dans une zone malloc()ée, que j'alloue qques objets dessus et que je la free(), le standard permet que ça leake ?

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

21

Sympa ces exercices, j'avais fait un petit fichier de test pour voir comment ça se comportait tout ça, et surtout pour tester si il était nécessaire de répéter le virtual dans les classes dérivées. J'en avais déduit qu'il était pas nécessaire mais qu'il était préférable de continuer à le marquer (en le répétant, on ne risque pas de l'oublier et ensuite de surcharger une méthode virtuelle).

Sinon, j'ai lu un bon bouquin pour éviter tout ces pièges (Effective C++ de Scott Meyers) et en particulier pour ce GotW, il recommande de ne jamais surcharger une méthode virtuelle, et de toujours mettre un destructeur virtuel pour les classes abstraites. D'ailleurs, j'ai été étonné de voir que GCC intégrait des warnings pour certaines recommandations du livre ( -Weffc++ ).
avatar
;)

22

Pollux> alors le new et le delete sont plus compliqués que ça. Tu as l'opérateur new de ta classe.
Et tu as six fonctions d'allocations, appelées new également, définies au niveau global.
Et enfin il faut distinguer ça des new-expression qui sont les bouts de code provoquant l'allocation (x = new Classewink

L'opérateur peut être redéfini, ces six fonctions également. Et il est possible de définir de nouvelles fonctions d'allocations au niveau global.

Quand le programme rencontre une new-expression, il génère la séquence suivante :

- la new-expression comment par allouer de la mémoire =>
---- si la classe a un opérateur new avec des paramètres correspondant, c'est lui qui est appelé
---- sinon le comportement par défaut s'applique =>
-------- la new-expression détermine la taille nécessaire. Pour un objet simple, c'est sizeof(objet), pour un tableau ça peut être davantage pour des questions d'alignement.
-------- la new-expression fait appel à la fonction d'allocation appropriée
- la new-expression initie la construction de l'objet

(on passera sur la gestion des exceptions intervenant dans l'allocation ou la construction...ainsi que sur la définition de la construction d'un objet)


Il faut repasser un coup sur "la fonction d'allocaton appropriée". Il en existe 6, et elles peuvent être redéfinies séparément. Elles sont :
void * operator new(std :: size_t size ) throw (std :: bad_alloc );              // new normal
void * operator new(std :: size_t size , const std :: nothrow_t & ) throw ();    // new nothrow
void * operator new (std :: size_t size , void * ptr ) throw ();                 // placement-new
void * operator new []( std :: size_t size ) throw (std :: bad_alloc );          // new tableau normal
void * operator new []( std :: size_t size , const std :: nothrow_t & ) throw ();// new tableau nothrow
void * operator new []( std :: size_t size , void * ptr ) throw ();              // placement-new tableau


1) Le new normal, je pense qu'il n'y a pas trop de commentaire à faire dessus, c'est quasiment un malloc (quoiqu'il obtienne sa mémoire depuis un autre pool en général), sauf qu'en cas d'échec d'allocation il lève une exception std::bad_alloc

2) Le new nothrow, c'est encore plus proche du malloc, vu qu'il renvoie 0 au lieu de lever une exception. On peut forcer la new-expression à utiliser le new nothrow de cette façon => X = new(std::nothrow) Classe;

3) le placement-new est utilisé pour allouer l'objet à l'adresse passée en paramètre

4 à 6) idem que les premiers mais pour des tableaux.


Au vu de ça, je pense que ce que tu proposais n'est pas de créer un opérateur new qui utilise malloc, vu que ça serait encore dans la sémantique new normale, mais plutôt d'allouer des objets dans une zone mémoire réservée avec malloc. Ca donnerait ceci =>
void * buffer = malloc(sizeof(Classe));
Classe * X = new(buffer) Classe;


Dans ce cas, pour libérer ton objet, tu dois faire ça =>
X->~Classe();
free(buffer);



En terme de standard c'est plus compliqué, ce qu'il se passe si tu n'appelles pas le destructeur n'est défini nulle part. La seule chose qui est dite, c'est que dans le cas d'un destructeur trivial (donc avec que des membres et classes de bases à destructeurs récursivement triviaux), le compilateur peut ne pas appeler les destructeurs.
Mais ça ne retire rien à ce que je disais, vu que le compilateur sait très bien s'il a ajouté ou non des choses qui font qu'il doit appeler impérativement les destructeurs triviaux; en revanche toi tu ne le sais pas.

23

En même temps ce peut être voulu de désalouer à l'aide de tel destructeur.

24

spectras :
En terme de standard c'est plus compliqué, ce qu'il se passe si tu n'appelles pas le destructeur n'est défini nulle part.

C'est ce que je voulais savoir ^^ (mais d'un autre côté, est-ce qu'il est dit à quelconque endroit que l'utilisateur doit appeler le destructeur à un moment ou à un autre ?)
La seule chose qui est dite, c'est que dans le cas d'un destructeur trivial (donc avec que des membres et classes de bases à destructeurs récursivement triviaux), le compilateur peut ne pas appeler les destructeurs.

Tiens, pourquoi est-ce que ça ne rentre pas dans la clause "as if" ? (i.e. on n'a aucun moyen de savoir si le compilo appelle effectivement le destructeur s'il est trivial, donc le compilo devrait avoir carte blanche même sans autorisation explicite de la norme, non ?)

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

25

C'est ce que je voulais savoir ^^ (mais d'un autre côté, est-ce qu'il est dit à quelconque endroit que l'utilisateur doit appeler le destructeur à un moment ou à un autre ?)
Non, mais il n'est pas non plus dit que tu dois libérer les objets que tu alloues dynamiquement.
Tiens, pourquoi est-ce que ça ne rentre pas dans la clause "as if" ? (i.e. on n'a aucun moyen de savoir si le compilo appelle effectivement le destructeur s'il est trivial, donc le compilo devrait avoir carte blanche même sans autorisation explicite de la norme, non ?)
Ben dans la norme ils donnent la formulation complémentaire, à savoir : si le destructeur n'est pas trivial, le compilateur doit forcément l'appeler. Par contre ça saoule j'ai vu ça tout à l'heure et là je voulais le citer je le retrouve plus :/

26

Ah oui, avec cette formulation-là c'est plus logique... Le "si le destructeur n'est pas trivial" est juste pour clarifier que c'est pas la peine de l'appeler quand il est trivial, même si ça n'est pas indispensable de le préciser ^^

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

27

spectras :
Commençons par le 2ème appel (le premier est bon évidememnt) :
=> Il va donner Derived::f(complex)

Marrant, mais j'aurais dit ça sans meme reflechir, vu que le type complexe est vu comme un double la (et pourtant les template et moi, ça fait 42) (apres j'aurais été incapable de dire si j'avais juste ou non grin)
avatar
Proud to be CAKE©®™


GCC4TI importe qui a problème en Autriche, pour l'UE plus et une encore de correspours nucléaire, ce n'est pas ytre d'instérier. L'état très même contraire, toujours reconstruire un pouvoir une choyer d'aucrée de compris le plus mite de genre, ce n'est pas moins)
Stalin est l'élection de la langie.

28

vu que le type complexe est vu comme un double la
Oui sauf que c'est pas ça la raison wink
La raison c'est que le fait de dériver l'une des fonctions masque toutes les autres.
Donc seule la fonction avec le complex existe lors de l'analyse.
Coup de bol (ou non, selon le point de vue), la classe complex définit un opérateur de conversion implicite depuis le type double.

29

Et c'est bien pour ça que j'utilise jamais les fonctions virtuelles, c'est vite la mort wink

Mais dans le cas ou la fonction de la classe mere n'est pas virtuel, il se passe quoi ? grin
avatar
Proud to be CAKE©®™


GCC4TI importe qui a problème en Autriche, pour l'UE plus et une encore de correspours nucléaire, ce n'est pas ytre d'instérier. L'état très même contraire, toujours reconstruire un pouvoir une choyer d'aucrée de compris le plus mite de genre, ce n'est pas moins)
Stalin est l'élection de la langie.

30

Bah pour la ligne 2, le fait qu'il y ait des fonctions virtuelles n'est pas impliqué, la résolution de la fonction est statique.
Donc absolument aucun changement ^^

Et sinon, si tu fais du développement objet, tu ne peux pas te passer des fonctions virtuelles, dès que tu commence à manipuler des pointeurs. Déjà, rien que le destructeur qui doit être virtuel si tu ne veux pas aller au devant de gros ennuis.