Ponteiro de função para função-membro

92

Gostaria de configurar um ponteiro de função como membro de uma classe que é um ponteiro para outra função na mesma classe. Os motivos pelos quais estou fazendo isso são complicados.

Neste exemplo, gostaria que a saída fosse "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())
}

Mas isso falha na compilação. Por quê?

Mike
fonte
@jww e verifique a resposta de CiroSantilli nessa questão, as outras respostas estão mais ou menos fora do tópico. Basicamente, apenas int (C :: * function_pointer_var) (int) = & C :: método; então C c; e (c. * função_pointer_var) (2).
jw_

Respostas:

160

A sintaxe está errada. Um ponteiro de membro é uma categoria de tipo diferente de um ponteiro comum. O ponteiro do membro deverá ser usado junto com um objeto de sua 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.xainda não diz em qual objeto a função deve ser chamada. Apenas diz que você deseja usar o ponteiro armazenado no objeto a. Anexar aoutro tempo como o operando à esquerda para o .*operador informará ao compilador em qual objeto chamar a função.

Johannes Schaub - litb
fonte
Eu sei que isso é antigo, mas não entendo o uso de (a.*a.x)().Por que (a.*x)()não funciona?
Gaurav Sehgal
3
@gau porque x não está no escopo
Johannes Schaub - litb
13
Também tenho que pesquisar isso toda vez que usá-lo. A sintaxe é confusa, mas faz sentido se você quebrá-la. a.xé um ponteiro para uma função-membro da classe A. *a.xdesreferencia o ponteiro, então agora é uma referência de função. a.(*a.x)"vincula" a função a uma instância (exatamente como a.f). (a.(*a.x))é necessário agrupar essa sintaxe complexa e (a.(*a.x))()realmente invocar o método asem argumentos.
jwm
23

int (*x)()não é um ponteiro para a função de membro. Um ponteiro para função de membro é escrito assim: int (A::*x)(void) = &A::f;.

Bertrand Marron
fonte
17

Chamar função membro no comando de string

#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;
}

Preste atenção (this->*func)();e a maneira de declarar o ponteiro de função com o nome da classevoid (A::*func)()

Ele para
fonte
11

Você precisa usar um ponteiro para uma função de membro, não apenas um ponteiro para uma função.

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
fonte
3

Embora isso seja baseado nas excelentes respostas em outras partes desta página, eu tive um caso de uso que não foi completamente resolvido por elas; para um vetor de ponteiros para funções, faça o seguinte:

#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;
}

Algo como isso é útil se você estiver escrevendo um interpretador de comandos com funções indexadas que precisam ser combinadas com a sintaxe de parâmetro e dicas de ajuda, etc. Possivelmente também útil em menus.

Coruja
fonte
1
Conforme definido, AFunc é um ponteiro para a função membro que recebe dois ints e retorna um vetor de ints. Mas os membros apontaram para retornar int, certo? Acho que a declaração de typedef deveria ser typedef int (A::*AFunc)(int I1,int I2);
riderBill
3

Embora você infelizmente não possa converter um ponteiro de função de membro existente em um ponteiro de função simples, você pode criar um modelo de função de adaptador de uma maneira bastante direta que envolve um ponteiro de função de membro conhecido em tempo de compilação em uma função normal como esta:

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>;

Observe que, para chamar a função de membro, uma instância de Adeve ser fornecida.

IllidanS4 suporta Monica
fonte
Você me inspirou, @ IllidanS4. Veja minha resposta. +1
memtha
1

Com base na resposta de @ IllidanS4, criei uma classe de modelo que permite que praticamente qualquer função de membro com argumentos predefinidos e instância de classe seja passada por referência para chamada posterior.



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)...)) {}
};

Teste / exemplo:

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
}

Obviamente, isso só funcionará se os argumentos fornecidos e a classe do proprietário ainda forem válidos. Quanto à legibilidade ... por favor, me perdoe.

Edit: removeu o malloc desnecessário, tornando o armazenamento normal da tupla. Adicionado tipo herdado para a referência. Adicionada opção para fornecer todos os argumentos no momento da chamada. Agora trabalhando para ter os dois ...

Editar 2: Como prometido, ambos. A única restrição (que vejo) é que os argumentos predefinidos devem vir antes dos argumentos fornecidos pelo tempo de execução na função de retorno de chamada. Obrigado a @Chipster por alguma ajuda com conformidade com o gcc. Isso funciona no gcc no ubuntu e no visual studio no 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
fonte