Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

cast de pointeur sur fonction

5 réponses
Avatar
ALB
bonjour =E0 tous,

voil=E0, j'ai une petite question qui intervient quand on cherche par
exemple =E0 faire une factory.

Si une classe B d=E9rive d'une classe A, pourquoi le compilo ne r=E9ussit-
il pas =E0 caster seul
un pointeur sur la fonction suivante :
B* f() { return new B(); };

en un A* (*)(void) ?
Dans la mesure o=F9 B d=E9rive de A, la conversion para=EEt simple, non ?

Toutefois, un reinterpret_cast semble fonctionner.

Un peu de code pour illustrer l'exemple :


#include<memory>

class Base {
public :
Base() {};
virtual ~Base() {};
virtual void id() { std::cout<<"Base"<<std::endl; };
};

class Derived : public Base {
public :
Derived():Base() {};
virtual ~Derived() {};
void id() { std::cout<<"Derived"<<std::endl; };
};

class NonDerived {
public :
NonDerived() {};
virtual ~NonDerived() {};
void id() { std::cout<<"NonDerived"<<std::endl; };
};


Base* func_Base() {
return new Base();
};

Derived* func_Derived() {
return new Derived();
};

NonDerived* func_NonDerived() {
return new NonDerived();
};

typedef Base* (*pBuilder)(void);

int main() {
pBuilder func1 =3D func_Base;
// pBuilder func2 =3D static_cast<pBuilder>(func_Derived); Ne compile
pas !
pBuilder func2 =3D reinterpret_cast<pBuilder>(func_Derived);
pBuilder func3 =3D reinterpret_cast<pBuilder>(func_NonDerived); //
compile (=E9videmment ...)
const std::auto_ptr<Base> ptr2((*func2)());
const std::auto_ptr<Base> ptr3((*func3)());
try {
ptr2->id();
}
catch (...) {
std::cout<<"erreur sur ptr2\n";
}

try {
ptr3->id(); // Aie (!)
}
catch (...) {
std::cout<<"erreur sur ptr3\n"; // on ne passe pas ici.
}
std::cout<<"fini"<<std::endl; // on ne passe pas ici.
};

5 réponses

Avatar
James Kanze
On Feb 12, 5:57 pm, ALB wrote:

voilà, j'ai une petite question qui intervient quand on
cherche par exemple à faire une factory.

Si une classe B dérive d'une classe A, pourquoi le compilo ne réussit-
il pas à caster seul
un pointeur sur la fonction suivante :
B* f() { return new B(); };

en un A* (*)(void) ?
Dans la mesure où B dérive de A, la conversion paraît simple, non ?


La conversion est simple. Utiliser le pointeur qui en résulte,
en revanche, a un comportement indéfini.

Toutefois, un reinterpret_cast semble fonctionner.


Un des comportements possibles d'un comportement indéfini. Je
parie que tu n'as pas essayé toutes les possibilités.

Essaie par exemple :

#include <iostream>

class WrongBase
{
public:
virtual ~WrongBase() {}
virtual void g() const ;
} ;

void
WrongBase::g() const
{
std::cout << "in WrongBase::g()" << std::endl ;
}

class Base
{
public:
virtual ~Base() {}
virtual void f() const ;
} ;

void
Base::f() const
{
std::cout << "in Base::f()" << std::endl ;
}

class Derived : public WrongBase, public Base
{
virtual void f() const ;
} ;

void
Derived::f() const
{
std::cout << "In Derived:f()" << std::endl ;
}

Derived*
factoryDerived()
{
return new Derived ;
}

int
main()
{
Base* (* factory)()
= reinterpret_cast< Base* (*)() >( &factoryDerived ) ;
Base* pBase = (*factory)() ;
pBase->f() ;
return 0 ;
}

Pas de problème à la compilation. Pas de crash non plus à
l'exécution. Mais la sortie n'est peut-être
pas tout à fait ce à laquelle on s'attendrait.

Dans d'autres cas, ça pourrait même provoquer un core dump.

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
ALB
On 12 fév, 19:44, James Kanze wrote:
On Feb 12, 5:57 pm, ALB wrote:

voilà, j'ai une petite question qui intervient quand on
cherche par exemple à faire une factory.
Si une classe B dérive d'une classe A, pourquoi le compilo ne réussi t-
il pas à caster seul
un pointeur sur la fonction suivante :
B* f() { return new B(); };
en un A* (*)(void) ?
Dans la mesure où B dérive de A, la conversion paraît simple, non ?


La conversion est simple. Utiliser le pointeur qui en résulte,
en revanche, a un comportement indéfini.

Toutefois, un reinterpret_cast semble fonctionner.


Un des comportements possibles d'un comportement indéfini. Je
parie que tu n'as pas essayé toutes les possibilités.

Essaie par exemple :

    #include <iostream>

    class WrongBase
    {
    public:
        virtual             ~WrongBase() {}
        virtual void        g() const ;
    } ;

    void
    WrongBase::g() const
    {
        std::cout << "in WrongBase::g()" << std::endl ;
    }

    class Base
    {
    public:
        virtual             ~Base() {}
        virtual void        f() const ;
    } ;

    void
    Base::f() const
    {
        std::cout << "in Base::f()" << std::endl ;
    }

    class Derived : public WrongBase, public Base
    {
        virtual void        f() const ;
    } ;

    void
    Derived::f() const
    {
        std::cout << "In Derived:f()" << std::endl ;
    }

    Derived*
    factoryDerived()
    {
        return new Derived ;
    }

    int
    main()
    {
        Base* (*           factory)()
            = reinterpret_cast< Base* (*)() >( &factoryDeriv ed ) ;
        Base*              pBase = (*factory)() ;
        pBase->f() ;
        return 0 ;
    }

Pas de problème à la compilation. Pas de crash non plus à
l'exécution. Mais la sortie n'est peut-être
pas tout à fait ce à laquelle on s'attendrait.

Dans d'autres cas, ça pourrait même provoquer un core dump.

--
James Kanze (GABI Software)             email: l.com
Conseils en informatique orientée objet/
                   Beratung in objektorientierter Date nverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Bel exemple !... donc le reinterpret_cast ne fait pas ce que
j'espérais (on ne peut vraiment pas compter sur lui ...)... à moins de
mettre WrongBase en second...
D'ailleurs, la norme garantit-elle que WrongBase et Base soient
"identiques" en mémoire ?

Je ne comprends cependant toujours pas pourquoi la conversion ne
s'opère pas comme je le souhaite...
Il y a bien une alternative de ce genre :

struct wrapper {
virtual Base* operator()(void) = 0;
virtual ~wrapper() {};
};

template<class T>
struct Wrapper : wrapper {
typedef T* (*Tptr)(void);
explicit Wrapper(Tptr builder) : wrapper(), _builder(builder) {};
Base* operator()(void) { return static_cast<Base*>(_builder());};
Tptr _builder;
};

mais je trouve cela cher en lignes de code et cela revient, plus ou
moins automatiquement, à écrire deux fonctions, une qui renvoie un
Derived* et une autre qui renvoie un Base*.
D'ailleurs, dans un tel Wrapper, le compilo n'est pas capable de
déterminer T tout seul, pourquoi ? Il n'est pourtant pas possible
d'overloader uniquement le type de retour d'une fonction ?

Adrien


Avatar
James Kanze
On Feb 12, 11:14 pm, ALB wrote:
On 12 fév, 19:44, James Kanze wrote:
On Feb 12, 5:57 pm, ALB wrote:

voilà, j'ai une petite question qui intervient quand on
cherche par exemple à faire une factory.
Si une classe B dérive d'une classe A, pourquoi le compilo ne réus sit-
il pas à caster seul
un pointeur sur la fonction suivante :
B* f() { return new B(); };
en un A* (*)(void) ?
Dans la mesure où B dérive de A, la conversion paraît simple, no n ?


La conversion est simple. Utiliser le pointeur qui en résulte,
en revanche, a un comportement indéfini.

Toutefois, un reinterpret_cast semble fonctionner.



Bel exemple !... donc le reinterpret_cast ne fait pas ce que
j'espérais (on ne peut vraiment pas compter sur lui ...)... à moins de
mettre WrongBase en second...
D'ailleurs, la norme garantit-elle que WrongBase et Base soient
"identiques" en mémoire ?


La norme ne dit rien sur les détails de l'implémentation. Une
implémentation est libre de disposer les éléments d'une classe
en mémoire comme il lui plaise, à très peu d'exceptions près.
Et l'organisation varie en fait d'un compilateur à l'autre.

Je ne comprends cependant toujours pas pourquoi la conversion
ne s'opère pas comme je le souhaite...


Principalement pour deux raisons : d'une part, parce que ce
serait prèsqu'impossible à implémenter, ou en tout cas très
cher, et de l'autre parce qu'il n'a pas de sens.

Il y a bien une alternative de ce genre :

struct wrapper {
virtual Base* operator()(void) = 0;
virtual ~wrapper() {};

};

template<class T>
struct Wrapper : wrapper {
typedef T* (*Tptr)(void);
explicit Wrapper(Tptr builder) : wrapper(), _builder(builder) {};
Base* operator()(void) { return static_cast<Base*>(_builder());};
Tptr _builder;
};

mais je trouve cela cher en lignes de code et cela revient,
plus ou moins automatiquement, à écrire deux fonctions, une
qui renvoie un Derived* et une autre qui renvoie un Base*.


À la fin, il te faut bien deux fonctions, parce qu'il y a deux
fonctionalités distinctes. Obtenir un Base*, ce n'est pas
obtenir un Derived*.

D'ailleurs, dans un tel Wrapper, le compilo n'est pas capable
de déterminer T tout seul, pourquoi ?


Et comment le ferait-il ? Et quel sens est-ce que ça aurait s'il
le pouvait ? Je ne vois toujours pas comment tu comptes
utiliser tout ça s'il fonctionnait.

Il n'est pourtant pas possible d'overloader uniquement le type
de retour d'une fonction ?


Non. D'autre langages (par exemple l'Ada) le supportent, mais
ils n'ont pas toutes les conversions implicites de C++. En C++,
ça risquerait de provoquer plus de confusion qu'autre chose.

On peut le simuler dans des cas bien précis :

class Owner
{
public:
class Proxy
{
public:
explicit Proxy( Owner const* owner )
: myOwner( owner )
{
}
template< typename T >
operator T() const
{
return myOwner->typedGet< T >() ;
}
private:
Owner const* myOwner ;
} ;

// ...
Proxy get() const
{
return Proxy( this ) ;
}
template< typename T >
T typedGet() const
{
// ...
}
} ;

Dans la pratique, il n'y a pas autant de cas où c'est
raisonable. Les fonctions get ont dans la pratique des
paramètres pour choisir une attribute parmi d'autres, et il se
pose toujours la question que faire si l'attribute choisie ne se
converte pas au type choisi.

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



Avatar
ALB
On 13 fév, 09:28, James Kanze wrote:
On Feb 12, 11:14 pm, ALB wrote:





On 12 fév, 19:44, James Kanze wrote:
On Feb 12, 5:57 pm, ALB wrote:
voilà, j'ai une petite question qui intervient quand on
cherche par exemple à faire une factory.
Si une classe B dérive d'une classe A, pourquoi le compilo ne ré ussit-
il pas à caster seul
un pointeur sur la fonction suivante :
B* f() { return new B(); };
en un A* (*)(void) ?
Dans la mesure où B dérive de A, la conversion paraît simple, non ?
La conversion est simple. Utiliser le pointeur qui en résulte,

en revanche, a un comportement indéfini.
Toutefois, un reinterpret_cast semble fonctionner.
Bel exemple !... donc le reinterpret_cast ne fait pas ce que


j'espérais (on ne peut vraiment pas compter sur lui ...)... à moins de
mettre WrongBase en second...
D'ailleurs, la norme garantit-elle que WrongBase et Base soient
"identiques" en mémoire ?


La norme ne dit rien sur les détails de l'implémentation. Une
implémentation est libre de disposer les éléments d'une classe
en mémoire comme il lui plaise, à très peu d'exceptions près.
Et l'organisation varie en fait d'un compilateur à l'autre.

Je ne comprends cependant toujours pas pourquoi la conversion
ne s'opère pas comme je le souhaite...


Principalement pour deux raisons : d'une part, parce que ce
serait prèsqu'impossible à implémenter, ou en tout cas très
cher, et de l'autre parce qu'il n'a pas de sens.





Il y a bien une alternative de ce genre :
struct wrapper {
        virtual Base* operator()(void) = 0;
        virtual ~wrapper() {};

};
template<class T>
struct Wrapper : wrapper {
        typedef T* (*Tptr)(void);
        explicit Wrapper(Tptr builder) : wrapper(), _builder(bui lder) {};
        Base* operator()(void) { return static_cast<Base*>(_buil der());};
        Tptr _builder;
};
mais je trouve cela cher en lignes de code et cela revient,
plus ou moins automatiquement, à écrire deux fonctions, une
qui renvoie un Derived* et une autre qui renvoie un Base*.


À la fin, il te faut bien deux fonctions, parce qu'il y a deux
fonctionalités distinctes. Obtenir un Base*, ce n'est pas
obtenir un Derived*.

D'ailleurs, dans un tel Wrapper, le compilo n'est pas capable
de déterminer T tout seul, pourquoi ?


Et comment le ferait-il ? Et quel sens est-ce que ça aurait s'il
le pouvait ?  Je ne vois toujours pas comment tu comptes
utiliser tout ça s'il fonctionnait.

Il n'est pourtant pas possible d'overloader uniquement le type
de retour d'une fonction ?


Non. D'autre langages (par exemple l'Ada) le supportent, mais
ils n'ont pas toutes les conversions implicites de C++. En C++,
ça risquerait de provoquer plus de confusion qu'autre chose.

On peut le simuler dans des cas bien précis :

    class Owner
    {
    public:
        class Proxy
        {
        public:
            explicit        Proxy( Owner const* owner )
                :   myOwner( owner )
            {
            }
            template< typename T >
                            operator T() const
            {
                return myOwner->typedGet< T >() ;
            }
        private:
            Owner const*    myOwner ;
        } ;

        //  ...
        Proxy get() const
        {
            return Proxy( this ) ;
        }
        template< typename T >
        T typedGet() const
        {
            //  ...
        }
    } ;

Dans la pratique, il n'y a pas autant de cas où c'est
raisonable.  Les fonctions get ont dans la pratique des
paramètres pour choisir une attribute parmi d'autres, et il se
pose toujours la question que faire si l'attribute choisie ne se
converte pas au type choisi.

--
James Kanze (GABI Software)             email: l.com
Conseils en informatique orientée objet/
                   Beratung in objektorientierter Date nverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34- M asquer le texte des messages précédents -

- Afficher le texte des messages précédents -- Masquer le texte des me ssages précédents -

- Afficher le texte des messages précédents -


En fait, si j'ai bien compris, le C++ convertit (implicitement) que
des objets et pas des fonctions (on convertit les objets pour les
adapter à la fonction mais on ne peut pas convertir la fonction pour
l'adapter aux objets). C'est pourquoi, mon histoire de conversion de
fonction n'a pas de sens.
Comme tu l'indiques, il faut donc bien deux fonctions une qui renvoie
un Base* et une qui renvoie un Derived*.

Le problème de reconnaissance du type de retour d'une fonction par un
template était dans ma compréhension un problème similaire (mais
effectivement sans lien direct avec le cas d'une factory).

Merci pour tes indications et tes exemples.

Adrien




Avatar
James Kanze
On Feb 13, 10:57 am, ALB wrote:
On 13 fév, 09:28, James Kanze wrote:
On Feb 12, 11:14 pm, ALB wrote:



[...]
En fait, si j'ai bien compris, le C++ convertit (implicitement) que
des objets et pas des fonctions (on convertit les objets pour les
adapter à la fonction mais on ne peut pas convertir la fonction pour
l'adapter aux objets).


Ça veut dire quoi, convertir une fonction. Quant le C++
convertit un objet, il crée un nouvel objet, distinct de l'objet
initial. (Il a, par exemple, une adresse différente, voire --
s'il n'a pas de type classe, pas d'adresse du tout.)

On pourrait effectivement imaginer quelque chose de semblable
pour des fonctions -- c'est même une des implémentations
possible pour des fonctions avec un type de rétour co-variant,
et certains compilateurs utilisent la même technique pour des
fonctions virtuelles en général. (Si tu as une fonction
Derived::f(), au niveau de l'implémentation, c'est en général la
même chose que f( Derived* ). Or, quand tu l'appelles avec
pBase->f(), ce que tu as, c'est un Base*, non un Derived*.
Alors, ou bien, le vtable contient assez d'informations pour
convertir le Base* en Derived* avant l'appel -- c'est ce que
faisait CFront, par exemple -- ou bien, le compilateur génère
une fonction dite trampoline qui fait la conversion et appelle
le bon f, et met l'adresse de cette fonction dans le vtable --
c'est le cas de g++, je crois.)

Le C++ ne supporte ce genre de chose (valeurs de retour
co-variant) que pour des fonctions membres, pas des fonctions
libres. Et je ne suis pas sûr que ça ferait ce que tu veux. Ça
permettrait la conversion d'un Derived* (*)() en un Base* (*)(),
et que quand tu appelles la fonction à travers le résultat de
cette conversion, tu aurais réelement un Base*, vers la partie
Base de l'objet, et non un Derived*.

C'est pourquoi, mon histoire de conversion de fonction n'a pas
de sens. Comme tu l'indiques, il faut donc bien deux
fonctions une qui renvoie un Base* et une qui renvoie un
Derived*.


Oui, mais celle qui renvoie le Base* ne fait qu'appeler celui
qui renvoie le Derived*. À la rigueur, on doit pouvoir le faire
au moyen d'un template.

Le problème de reconnaissance du type de retour d'une fonction
par un template était dans ma compréhension un problème
similaire (mais effectivement sans lien direct avec le cas
d'une factory).


Je crois que c'est le problème qui se poserait pour résoudre le
problème avec un template aussi.

Si tu peux accepter un pointeur à une objet fonctionnel, plutôt
qu'à une fonction, quelque chose comme le suivant pourrait
peut-être faire l'affaire :

class AbstractFactory
{
public:
virtual ~AbstractFactory() {}
virtual Base* operator()() const = 0 ;
} ;

template< typename Derived >
class Factory : public AbstractFactory
{
public:
explicit Factory( Derived* (*pf)() )
: pf( pf )
{
}
virtual Base* operator()() const
{
return (*pf)() ;
}

private:
Derived* (* pf) () ;
} ;

template< typename Derived >
Factory< Derived >
baseFactory( Derived* (*pf)() )
{
return new Factory< Derived >( pf ) ;
}

Dans la pratique, en revanche, chaque fois qu'il m'a fallu des
pointeurs à des factory, c'était pour les mettre dans un map,
avec la plupart du temps, une chaîne de caractères comme clé.
Dans ces cas-là, je donnais à AbstractFactory un constructeur
(protégé) qui prenait une chaîne, et qui insérer l'objet dans le
map.

Aussi, ce genre de solution n'a en général de sens que si tu
travailles surtout avec des Base*. Il n'y a pas besoin d'une
fonction factory qui renvoie une Derived*, parce qu'à l'endroit
où tu vas appeler la fonction, tu vas de toute façon affecter le
résultat à un Base*.

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34