Pointeur de fonction vers la fonction membre

89

Je voudrais configurer un pointeur de fonction en tant que membre d'une classe qui est un pointeur vers une autre fonction de la même classe. Les raisons pour lesquelles je fais cela sont compliquées.

Dans cet exemple, je voudrais que la sortie soit "1"

class A {
public:
 int f();
 int (*x)();
}

int A::f() {
 return 1;
}


int main() {
 A a;
 a.x = a.f;
 printf("%d\n",a.x())
}

Mais cela échoue à la compilation. Pourquoi?

Mike
la source
@jww et vérifiez la réponse de CiroSantilli à cette question, les autres réponses sont plus ou moins hors sujet. Fondamentalement, juste int (C :: * function_pointer_var) (int) = & C :: method; puis C c; et (c. * fonction_pointer_var) (2).
jw_

Réponses:

157

La syntaxe est erronée. Un pointeur de membre est une catégorie de type différente d'un pointeur ordinaire. Le pointeur de membre devra être utilisé avec un objet de sa classe:

class A {
public:
 int f();
 int (A::*x)(); // <- declare by saying what class it is a pointer to
};

int A::f() {
 return 1;
}


int main() {
 A a;
 a.x = &A::f; // use the :: syntax
 printf("%d\n",(a.*(a.x))()); // use together with an object of its class
}

a.xne dit pas encore sur quel objet la fonction doit être appelée. Il indique simplement que vous souhaitez utiliser le pointeur stocké dans l'objet a. La pré-attente aune autre fois en tant qu'opérande de gauche à l' .*opérateur indiquera au compilateur sur quel objet appeler la fonction.

Johannes Schaub - litb
la source
Je sais que c'est vieux, mais je ne comprends pas l'utilisation de (a.*a.x)().Pourquoi ne (a.*x)()fonctionne pas?
Gaurav Sehgal
3
@gau parce que x n'est pas dans la portée
Johannes Schaub - litb
13
Je dois vérifier cela à chaque fois que je l'utilise également. La syntaxe est déroutante, mais elle a du sens si vous la décomposez. a.xest un pointeur vers une fonction membre de la classe A. *a.xdéréférence le pointeur donc maintenant c'est une référence de fonction. a.(*a.x)"lie" la fonction à une instance (tout comme a.f). (a.(*a.x))est nécessaire pour regrouper cette syntaxe complexe et (a.(*a.x))()invoque en fait la méthode asans argument.
jwm
23

int (*x)()n'est pas un pointeur vers une fonction membre. Un pointeur vers une fonction membre est écrit comme ceci: int (A::*x)(void) = &A::f;.

Bertrand Marron
la source
17

Appel d'une fonction membre sur une commande de chaîne

#include <iostream>
#include <string>


class A 
{
public: 
    void call();
private:
    void printH();
    void command(std::string a, std::string b, void (A::*func)());
};

void A::printH()
{
    std::cout<< "H\n";
}

void A::call()
{
    command("a","a", &A::printH);
}

void A::command(std::string a, std::string b, void (A::*func)())
{
    if(a == b)
    {
        (this->*func)();
    }
}

int main()
{
    A a;
    a.call();
    return 0;
}

Faites attention à (this->*func)();et à la manière de déclarer le pointeur de fonction avec le nom de la classevoid (A::*func)()

Heto
la source
11

Vous devez utiliser un pointeur vers une fonction membre, pas seulement un pointeur vers une fonction.

class A { 
    int f() { return 1; }
public:
    int (A::*x)();

    A() : x(&A::f) {}
};

int main() { 
   A a;
   std::cout << (a.*a.x)();
   return 0;
}
Jerry Coffin
la source
3

Bien que cela soit basé sur les réponses solides ailleurs sur cette page, j'avais un cas d'utilisation qui n'a pas été complètement résolu par eux; pour un vecteur de pointeurs vers des fonctions, procédez comme suit:

#include <iostream>
#include <vector>
#include <stdio.h>
#include <stdlib.h>

class A{
public:
  typedef vector<int> (A::*AFunc)(int I1,int I2);
  vector<AFunc> FuncList;
  inline int Subtract(int I1,int I2){return I1-I2;};
  inline int Add(int I1,int I2){return I1+I2;};
  ...
  void Populate();
  void ExecuteAll();
};

void A::Populate(){
    FuncList.push_back(&A::Subtract);
    FuncList.push_back(&A::Add);
    ...
}

void A::ExecuteAll(){
  int In1=1,In2=2,Out=0;
  for(size_t FuncId=0;FuncId<FuncList.size();FuncId++){
    Out=(this->*FuncList[FuncId])(In1,In2);
    printf("Function %ld output %d\n",FuncId,Out);
  }
}

int main(){
  A Demo;
  Demo.Populate();
  Demo.ExecuteAll();
  return 0;
}

Quelque chose comme ceci est utile si vous écrivez un interpréteur de commandes avec des fonctions indexées qui doivent être associées à la syntaxe des paramètres et des conseils d'aide, etc. Peut-être aussi utile dans les menus.

Hibou
la source
1
Tel que défini, AFunc est un pointeur vers une fonction membre prenant deux entiers et renvoyant un vecteur d'entiers. Mais les membres ont indiqué retourner int, non? Je pense que l'instruction typedef devrait être typedef int (A::*AFunc)(int I1,int I2);
riderBill
2

Bien que vous ne puissiez malheureusement pas convertir un pointeur de fonction membre existant en un pointeur de fonction simple, vous pouvez créer un modèle de fonction adaptateur d'une manière assez simple qui encapsule un pointeur de fonction membre connu au moment de la compilation dans une fonction normale comme celle-ci:

template <class Type>
struct member_function;

template <class Type, class Ret, class... Args>
struct member_function<Ret(Type::*)(Args...)>
{
    template <Ret(Type::*Func)(Args...)>
    static Ret adapter(Type &obj, Args&&... args)
    {
        return (obj.*Func)(std::forward<Args>(args)...);
    }
};

template <class Type, class Ret, class... Args>
struct member_function<Ret(Type::*)(Args...) const>
{
    template <Ret(Type::*Func)(Args...) const>
    static Ret adapter(const Type &obj, Args&&... args)
    {
        return (obj.*Func)(std::forward<Args>(args)...);
    }
};

 

int (*func)(A&) = &member_function<decltype(&A::f)>::adapter<&A::f>;

Notez que pour appeler la fonction membre, une instance de Adoit être fournie.

IllidanS4 prend en charge Monica
la source
Vous m'avez inspiré, @ IllidanS4. Voyez ma réponse. +1
memtha
1

En m'appuyant sur la réponse de @ IllidanS4, j'ai créé une classe modèle qui permet à pratiquement n'importe quelle fonction membre avec des arguments prédéfinis et une instance de classe d'être passée par référence pour un appel ultérieur.



template<class RET, class... RArgs> class Callback_t {
public:
    virtual RET call(RArgs&&... rargs) = 0;
    //virtual RET call() = 0;
};

template<class T, class RET, class... RArgs> class CallbackCalltimeArgs : public Callback_t<RET, RArgs...> {
public:
    T * owner;
    RET(T::*x)(RArgs...);
    RET call(RArgs&&... rargs) {
        return (*owner.*(x))(std::forward<RArgs>(rargs)...);
    };
    CallbackCalltimeArgs(T* t, RET(T::*x)(RArgs...)) : owner(t), x(x) {}
};

template<class T, class RET, class... Args> class CallbackCreattimeArgs : public Callback_t<RET> {
public:
    T* owner;
    RET(T::*x)(Args...);
    RET call() {
        return (*owner.*(x))(std::get<Args&&>(args)...);
    };
    std::tuple<Args&&...> args;
    CallbackCreattimeArgs(T* t, RET(T::*x)(Args...), Args&&... args) : owner(t), x(x),
        args(std::tuple<Args&&...>(std::forward<Args>(args)...)) {}
};

Test / exemple:

class container {
public:
    static void printFrom(container* c) { c->print(); };
    container(int data) : data(data) {};
    ~container() {};
    void print() { printf("%d\n", data); };
    void printTo(FILE* f) { fprintf(f, "%d\n", data); };
    void printWith(int arg) { printf("%d:%d\n", data, arg); };
private:
    int data;
};

int main() {
    container c1(1), c2(20);
    CallbackCreattimeArgs<container, void> f1(&c1, &container::print);
    Callback_t<void>* fp1 = &f1;
    fp1->call();//1
    CallbackCreattimeArgs<container, void, FILE*> f2(&c2, &container::printTo, stdout);
    Callback_t<void>* fp2 = &f2;
    fp2->call();//20
    CallbackCalltimeArgs<container, void, int> f3(&c2, &container::printWith);
    Callback_t<void, int>* fp3 = &f3;
    fp3->call(15);//20:15
}

Évidemment, cela ne fonctionnera que si les arguments donnés et la classe propriétaire sont toujours valides. En ce qui concerne la lisibilité ... veuillez me pardonner.

Éditer: supprimé malloc inutile en faisant le stockage normal du tuple. Ajout d'un type hérité pour la référence. Ajout d'une option pour fournir tous les arguments au moment de l'appel à la place. Je travaille maintenant à avoir les deux ...

Edit 2: Comme promis, les deux. La seule restriction (que je vois) est que les arguments prédéfinis doivent venir avant les arguments fournis par l'exécution dans la fonction de rappel. Merci à @Chipster pour son aide sur la conformité gcc. Cela fonctionne sur gcc sur ubuntu et Visual Studio sur Windows.

#ifdef _WIN32
#define wintypename typename
#else
#define wintypename
#endif

template<class RET, class... RArgs> class Callback_t {
public:
    virtual RET call(RArgs... rargs) = 0;
    virtual ~Callback_t() = default;
};

template<class RET, class... RArgs> class CallbackFactory {
private:
    template<class T, class... CArgs> class Callback : public Callback_t<RET, RArgs...> {
    private:
        T * owner;
        RET(T::*x)(CArgs..., RArgs...);
        std::tuple<CArgs...> cargs;
        RET call(RArgs... rargs) {
            return (*owner.*(x))(std::get<CArgs>(cargs)..., rargs...);
        };
    public:
        Callback(T* t, RET(T::*x)(CArgs..., RArgs...), CArgs... pda);
        ~Callback() {};
    };
public:
    template<class U, class... CArgs> static Callback_t<RET, RArgs...>* make(U* owner, CArgs... cargs, RET(U::*func)(CArgs..., RArgs...));
};
template<class RET2, class... RArgs2> template<class T2, class... CArgs2> CallbackFactory<RET2, RArgs2...>::Callback<T2, CArgs2...>::Callback(T2* t, RET2(T2::*x)(CArgs2..., RArgs2...), CArgs2... pda) : x(x), owner(t), cargs(std::forward<CArgs2>(pda)...) {}
template<class RET, class... RArgs> template<class U, class... CArgs> Callback_t<RET, RArgs...>* CallbackFactory<RET, RArgs...>::make(U* owner, CArgs... cargs, RET(U::*func)(CArgs..., RArgs...)) {
    return new wintypename CallbackFactory<RET, RArgs...>::Callback<U, CArgs...>(owner, func, std::forward<CArgs>(cargs)...);
}
memtha
la source