5 votes

Pointeur de fonction membre de classe C++ vers pointeur de fonction

J'utilise luabind comme wrapper Lua vers C++. Luabind offre une méthode pour utiliser ma propre fonction callback pour gérer les exceptions lancées par lua, set_pcall_callback(). J'ai donc paraphrasé un exemple de la documentation, les changements étant la fonction logger->log() et le fait de mettre la fonction dans une classe appelée 'Engine', donc au lieu d'être une fonction globale normale, c'est maintenant une fonction membre, ce qui semble être l'origine de mon problème.

Voici les extraits de code pertinents :

class Engine //Whole class not shown for brevity
{
public:
    Engine();
    ~Engine();
    void Run();
    int pcall_log(lua_State*);
private:
    ILogger *logger;
};

Engine::Run()
{
lua_State* L = lua_open();
luaL_openlibs(L);
open(L);
luabind::set_pcall_callback(&Engine::pcall_log); //<--- Problem line
//etc...rest of the code not shown for brevity
}

int Engine::pcall_log(lua_State *L)
{
lua_Debug d;
lua_getstack( L,1,&d);
lua_getinfo( L, "Sln", &d);
lua_pop(L, 1);
stringstream ss;
ss.clear();
ss.str("");
ss << d.short_src;
ss << ": ";
ss << d.currentline;
ss << ": ";
if ( d.name != 0)
{
    ss << d.namewhat;
    ss << " ";
    ss << d.name;
    ss << ") ";
}
ss << lua_tostring(L, -1);
logger->log(ss.str().c_str(),ELL_ERROR);
return 1;
}

Voici ce que dit le compilateur pendant la compilation :

C:\pb\engine.cpp|31|error: cannot convert 'int (Engine::*)(lua_State*)' to 'int (*)(lua_State*)' for argument '1' to 'void luabind::set_pcall_callback(int (*)(lua_State*))'|

Il semble donc que l'erreur soit due au fait que la fonction attend un pointeur de fonction ordinaire, et non un pointeur de fonction de membre de classe. Existe-t-il un moyen de couler ou d'utiliser un pointeur de fonction intermédiaire à transmettre à la fonction set_pcall_callback() ?

Merci !

15voto

Kerrek SB Points 194696

Non. Une fonction membre n'est pas une fonction libre. Le type est entièrement différent, et un pointeur vers une fonction membre (PTMF) est un objet complètement différent et incompatible avec un pointeur de fonction. (Plus important encore, un pointeur vers une fonction membre doit toujours être utilisé avec un pointeur d'instance vers l'objet dont vous voulez appeler le membre. utiliser un PTMF de la même manière que vous utilisez un pointeur de fonction.

La solution la plus simple pour interagir avec le code C est d'écrire une fonction d'enveloppe globale qui répartit votre appel, ou de faire en sorte que votre fonction membre statique (dans ce cas, elle devient essentiellement une fonction libre) :

// global!

Engine * myEngine;
int theCallback(lua_State * L)
{
  return myEngine->pcall_log(L);
}

Engine::Run()
{
  /* ... */
  myEngine = this;
  luabind::set_pcall_callback(&theCallback);
  /* ... */
}

Le problème conceptuel ici est que vous avez un moteur classe bien que vous n'en ayez pratiquement qu'une seule instance. Pour une véritable classe avec de nombreux objets, un PTMF n'aurait pas de sens car vous devriez spécifier quel objet utiliser pour l'appel, alors que votre classe moteur est peut-être essentiellement une classe singleton qui pourrait être entièrement statique (c'est-à-dire un espace de noms glorifié).

3voto

David Gausmann Points 747

Cela ne convient pas à votre problème LUA, mais peut-être à d'autres bibliothèques : Si une fonction demande un pointeur de fonction comme func(void* param, ...) et que vous pouvez vous assurer que la durée de vie de votre objet est plus grande que le pointeur de fonction stocké, alors vous pourriez techniquement aussi utiliser un pointeur de méthode (il a la même apparence sur la pile), mais C++ empêche le casting direct des pointeurs de méthode vers les pointeurs de fonction.

Mais avec une petite astuce, vous pouvez également convertir les pointeurs de méthode en pointeurs de fonction :

template<typename M> inline void* GetMethodPointer(M ptr)
{
    return *reinterpret_cast<void**>(&ptr);
}

En utilisant cela, vous pouvez utiliser des pointeurs de méthode, par exemple avec libmicrohttpd :

this->m_pDaemon = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, this->m_wPort, NULL, NULL, reinterpret_cast<MHD_AccessHandlerCallback>(GetMethodPointer(&CMyWebServer::AccessHandlerCallback)), this, MHD_OPTION_END);

Mais soyez-en conscient. Vous devez prendre soin de la durée de vie de cet objet. Les conventions d'appel doivent également correspondre.

0voto

sergio Points 52422

En tant que callback, il est habituel d'utiliser static function s :

class Engine //Whole class not shown for brevity
{
    ....
    static int pcall_log(lua_State*);
    ...
}

Cela résoudrait votre problème.

0voto

Nik-Lz Points 1245

La conversion explicite d'un pointeur de méthode en pointeur de fonction est illégale en C++ - point final.

Mais il y a un hack. Nous devons d'abord convertir le pointeur de méthode (const) en void* (const) et ensuite en pointeur de fonction (const). Et cela fonctionne. Et pourquoi ça ne marcherait pas ? Tout, et je dis bien tout, peut être pointé par une balise void* parce que tout a une adresse.


AVERTISSEMENT : Ce qui suit est un territoire de hack DAAEINGEROUS. Si vous développez un logiciel pour un avion de chasse ou autre, vous devriez savoir qu'il vaut mieux ne pas l'utiliser. Je ne suis pas responsable ! Je fournis juste ceci ici à des fins éducatives.


L'astuce est que nous devons convertir entre le pointeur de méthode et le pointeur de fonction par une conversion intermédiaire, potentiellement qualifiée de cv (const-volatile), void* .

De cette façon, nous sommes en mesure d'appeler une fonction membre (pointeur) par le biais d'un pointeur de fonction, avec le premier argument étant un Type* vers l'objet de la classe cible, qui est équivalent à l'objet de l'appel de la fonction membre. this* .

Compte tenu d'un : MethodPointerType f;

puis

FunctionPointerType m_pFn = reinterpret_cast<FunctionPointerType>( reinterpret_cast<void*&>( f ) );

ou, pour être plus explicite, utilisez deux des éléments suivants dans l'ordre, pour les fonctions membres non-const et const :

template<typename MP>
void* getMethodVoidPointer( MP ptr )
{
    return *reinterpret_cast<void**>( &ptr );
}
template<typename FP>
FP getFunctionPointer( void* p )
{
    return reinterpret_cast<FP>( p );
}
template<typename MP>
const void* getMethodConstVoidPointer( MP ptr )
{
    return *reinterpret_cast<const void**>( &ptr );
}
template<typename FP>
FP getConstFunctionPointer( const void* p )
{
    return reinterpret_cast<FP>( p );
}

Jouez avec en direct ici avec un échantillon complet compilable en C++17 : https://onlinegdb.com/HybR8crqw

Cela fonctionne aussi dans Visual Studio.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X