2009-08-20 5 views
11

Je suis actuellement en train d'utiliser GCC 4.4, et j'ai beaucoup de casse-tête entre void * et un pointeur vers la fonction membre. Je suis en train d'écrire une bibliothèque facile à utiliser pour la liaison des objets C++ à un interprète Lua, comme ceci:Coulée entre void * et un pointeur vers la fonction membre

LuaObject<Foo> lobj = registerObject(L, "foo", fooObject); 
lobj.addField(L, "bar", &Foo::bar); 

J'ai la plus grande partie fait, à l'exception de la fonction suivante (qui est spécifique à une certaine signature de la fonction jusqu'à ce que j'ai une chance de le généraliser):

template <class T> 
int call_int_function(lua_State *L) 
{ 
    // this next line is problematic 
    void (T::*method)(int, int) = reinterpret_cast<void (T::*)(int, int)>(lua_touserdata(L, lua_upvalueindex(1))); 
    T *obj = reinterpret_cast<T *>(lua_touserdata(L, 1)); 

    (obj->*method)(lua_tointeger(L, 2), lua_tointeger(L, 3)); 
    return 0; 
} 

pour ceux qui ne connaissent pas Lua, lua_touserdata(L, lua_upvalueindex(1)) obtient la première valeur associée à une fermeture (dans ce cas, il est le pointeur de fonction membre) et le retourne comme vide *. GCC se plaint que void * -> void (T :: *) (int, int) est une distribution invalide. Des idées sur la façon de contourner cela?

+5

http://www.parashift.com/c++-faq-lite/pointers-to-members .html –

+0

+1 ci-dessus ... spécifiquement section 33.7 & 33.8 – fbrereto

+1

Juste par curiosité, pourquoi essayez-vous de stocker des fonctions C dans Lua userdata comme ça? il y a probablement un moyen plus sûr d'atteindre votre objectif. – Alex

Répondre

17

Vous cannot cast a pointer-to-member to void * ou tout autre type de pointeur "normal". Les pointeurs vers les membres ne sont pas des adresses comme les pointeurs réguliers. Ce que vous devrez probablement faire est d'envelopper votre fonction de membre dans une fonction régulière. La FAQ C++ Lite explains this dans certains détails. Le problème principal est que les données nécessaires pour implémenter un pointeur vers un membre ne sont pas seulement une adresse, et en fait varies tremendously basée sur l'implémentation du compilateur.

Je présume que vous avez le contrôle sur ce que les données utilisateur lua_touserdata renvoie. Cela ne peut pas être un pointeur vers un membre, car il n'y a pas de façon légale de récupérer cette information. Mais vous avez d'autres choix:

  • Le choix le plus simple est probablement d'envelopper votre fonction membre dans une fonction gratuite et de retourner cela. Cette fonction libre devrait prendre l'objet comme premier argument. Voir l'exemple de code ci-dessous.

  • Utilisez une technique similaire à celle de Boost.Bind's mem_fun pour renvoyer un objet fonction, que vous pouvez modéliser de façon appropriée. Je ne vois pas que c'est plus facile, mais cela vous permettrait d'associer plus d'état avec la fonction return si vous en aviez besoin.

est ici une ré-écriture de votre fonction en utilisant la première façon:

template <class T> 
int call_int_function(lua_State *L) 
{ 
    void (*method)(T*, int, int) = reinterpret_cast<void (*)(T*, int, int)>(lua_touserdata(L, lua_upvalueindex(1))); 
    T *obj = reinterpret_cast<T *>(lua_touserdata(L, 1)); 

    method(obj, lua_tointeger(L, 2), lua_tointeger(L, 3)); 
    return 0; 
} 
+0

Je ne crois pas que le pointeur vers les fonctions membres soit différent des pointeurs normaux. Qu'est-ce qui vous fait penser qu'ils ont des propriétés spéciales? Ils pointent simplement sur un morceau de code. –

+0

Martin J'ai été formé sur ce point récemment. Permettez-moi de vous montrer la discussion ici: http://stackoverflow.com/questions/1207106/calling-base-class-definition-of-virtual-member-function-with-function-pointer/1207396#1207396. – quark

+2

Martin: Lire aussi le lien marqué "varie énormément": http://www.codeproject.com/KB/cpp/FastDelegate.aspx. Les pointeurs vers les fonctions membres ne sont pas nécessairement implémentés comme des pointeurs du tout, ni même de la même manière d'un système à l'autre. Ils peuvent être des combinaisons d'une table et d'un index, pleins sur des thunks ou l'un quelconque d'un certain nombre d'implémentations de variantes. – quark

1

Pour contourner ce problème étant donné les restrictions de la coulée d'un pointeur à fonction membre à void* vous pouvez envelopper le pointeur de fonction un petit tas alloué struct et de mettre un pointeur sur cette struct dans vos données utilisateur Lua:

template <typename T> 
struct LuaUserData { 
    typename void (T::*MemberProc)(int, int); 

    explicit LuaUserData(MemberProc proc) : 
     mProc(proc) 
    { } 

    MemberProc mProc; 
}; 

LuaObject<Foo> lobj = registerObject(L, "foo", fooObject); 
LuaUserData<Foo>* lobj_data = new LuaUserData<Foo>(&Foo::bar); 

lobj.addField(L, "bar", lobj_data); 

// ... 

template <class T> 
int call_int_function(lua_State *L) 
{ 
    typedef LuaUserData<T>      LuaUserDataType; 
    typedef typename LuaUserDataType::MemberProc ProcType; 

    // this next line is problematic 
    LuaUserDataType* data = 
     reinterpret_cast<LuaUserDataType*>(lua_touserdata(L, lua_upvalueindex(1))); 
    T *obj = reinterpret_cast<T *>(lua_touserdata(L, 1)); 

    (obj->*(data.mMemberProc))(lua_tointeger(L, 2), lua_tointeger(L, 3)); 
    return 0; 
} 

Je ne suis pas avertis avec Lua donc j'ai probablement oublié quelque chose dans l'exemple ci-dessus. Gardez à l'esprit, aussi, si vous suivez cette route, vous devrez gérer l'allocation de LuaUserData.

4

Il est possible de convertir le pointeur aux fonctions membres et attributs à l'aide des syndicats:

// helper union to cast pointer to member 
template<typename classT, typename memberT> 
union u_ptm_cast { 
    memberT classT::*pmember; 
    void *pvoid; 
}; 

Pour convertir, mettre la valeur source dans un membre, et tirer la valeur cible de l'autre.

Bien que cette méthode soit pratique, je ne sais pas si cela va fonctionner dans tous les cas.

+2

Je crois que les pointeurs membres sont en fait des objets de grande taille; il est très probable que 'sizeof (void *)

+1

J'ai essayé cette approche avec le CTP novembre de MSVC. J'ai ajouté un 'static_assert' pour m'assurer que' sizeof (u_ptm_cast :: pmember) == sizeof (u_ptm_cast :: pvoid) '. Il semble que cela fonctionne dans la plupart des situations, par ex. quand pmember est non-virtuel, virtuel ou quand 'classT' est substitué à une classe différente lors de la conversion à un pointeur de fonction membre. Cependant, si 'classT' utilise un héritage multiple, le pointeur est en effet plus grand et l'assertion échoue. –

+0

Sous GCC 4.7, tous les pointeurs de fonction membre semblent avoir la taille de deux pointeurs réguliers. Compte tenu de cela en faisant 'pvoid' un tableau de taille double, la technique de @ paniq impliquant une' union' fonctionne pour les mêmes cas qu'avec le compilateur MS. Dans l'ensemble cependant, c'est une solution assez fragile et pas très utile dans le problème de liaison Lua. –

1

Contrairement à l'adresse d'une non statique fonction membre, qui est un type pointeur à membre d'une représentation complexe, l'adresse d'une fonction membre statique est généralement juste l'adresse de la machine, compatible avec une conversion à void *.

Si vous devez lier une fonction membre C++ non statique à un mécanisme de rappel de type C ou C basé sur void *, vous pouvez écrire un encapsuleur statique à la place.

L'emballage peut prendre un pointeur vers une instance comme argument, et passer le contrôle à la fonction membre non statique:

void myclass::static_fun(myclass *instance, int arg) 
{ 
    instance->nonstatic_fun(arg); 
} 
+0

J'ai vu ce modèle dans diverses bibliothèques, mais comment le pointeur d'instance est-il rempli dans la fonction de rappel? La magie du compilateur? – andig

+1

@andig L'interface de rappel doit avoir un argument de contexte pour le passage des données utilisateur enregistrées. C'est à dire. "s'il vous plaît passez-moi mon pointeur quand vous me rappelez". Si l'interface de rappel n'a pas ceci, il peut être piraté avec des trampolines. Vous pouvez placer un petit morceau de code machine (le trampoline) à l'avant de l'objet de rappel, et ce code machine est utilisé comme fonction de rappel.Il calcule le pointeur d'objet par rapport à son pointeur d'instruction, sachant que l'objet est à un décalage fixe par rapport à sa propre adresse. Ensuite, il appelle la fonction réelle. – Kaz

0

Ici, il suffit de changer les paramètres de la fonction void_cast pour qu'il corresponde à vos besoins:

template<typename T, typename R> 
void* void_cast(R(T::*f)()) 
{ 
    union 
    { 
     R(T::*pf)(); 
     void* p; 
    }; 
    pf = f; 
    return p; 
} 

exemple d'utilisation:

auto pvoid = void_cast(&Foo::foo); 
Questions connexes