Funktionszeiger auf Elementfunktion

88

Ich möchte einen Funktionszeiger als Mitglied einer Klasse einrichten, die ein Zeiger auf eine andere Funktion in derselben Klasse ist. Die Gründe, warum ich das mache, sind kompliziert.

In diesem Beispiel möchte ich, dass die Ausgabe "1" ist.

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())
}

Dies schlägt jedoch beim Kompilieren fehl. Warum?

Mike
quelle
@jww und überprüfen Sie die Antwort von CiroSantilli in dieser Frage, andere Antworten sind mehr oder weniger vom Thema entfernt. Grundsätzlich ist nur int (C :: * function_pointer_var) (int) = & C :: method; dann C c; und (c. * function_pointer_var) (2).
jw_

Antworten:

156

Die Syntax ist falsch. Ein Elementzeiger ist eine andere Typkategorie als ein gewöhnlicher Zeiger. Der Elementzeiger muss zusammen mit einem Objekt seiner Klasse verwendet werden:

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.xsagt noch nicht, auf welchem ​​Objekt die Funktion aufgerufen werden soll. Es heißt nur, dass Sie den im Objekt gespeicherten Zeiger verwenden möchten a. Wenn Sie adem .*Operator eine andere Zeit als linken Operanden voranstellen, teilt dies dem Compiler mit, für welches Objekt die Funktion aufgerufen werden soll.

Johannes Schaub - litb
quelle
Ich weiß, dass dies alt ist, aber ich verstehe die Verwendung von nicht. (a.*a.x)()Warum funktioniert (a.*x)()nicht?
Gaurav Sehgal
3
@ Gau, weil x nicht im Geltungsbereich ist
Johannes Schaub - Litb
13
Ich muss das jedes Mal nachschlagen, wenn ich es auch benutze. Die Syntax ist verwirrend, aber es macht Sinn, wenn Sie sie aufschlüsseln. a.xist ein Zeiger auf eine Mitgliedsfunktion der Klasse A. *a.xDereferenziert den Zeiger, sodass er jetzt eine Funktionsreferenz ist. a.(*a.x)"bindet" die Funktion an eine Instanz (genau wie a.f). (a.(*a.x))ist erforderlich, um diese komplexe Syntax zu gruppieren, und (a.(*a.x))()ruft die Methode tatsächlich aohne Argumente auf.
JWM
23

int (*x)()ist kein Zeiger auf die Mitgliedsfunktion. Ein Zeiger auf die Member-Funktion lautet wie folgt : int (A::*x)(void) = &A::f;.

Bertrand Marron
quelle
16

Rufen Sie die Member-Funktion für den String-Befehl auf

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

Achten Sie auf (this->*func)();und wie Sie den Funktionszeiger mit dem Klassennamen deklarierenvoid (A::*func)()

Heto
quelle
11

Sie müssen einen Zeiger auf eine Elementfunktion verwenden, nicht nur einen Zeiger auf eine Funktion.

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 Sarg
quelle
3

Während dies auf den Sterling-Antworten an anderer Stelle auf dieser Seite basiert, hatte ich einen Anwendungsfall, der von ihnen nicht vollständig gelöst wurde. Gehen Sie für einen Vektor von Zeigern auf Funktionen wie folgt vor:

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

So etwas ist nützlich, wenn Sie einen Befehlsinterpreter mit indizierten Funktionen schreiben, die mit Parametersyntax und Hilfetipps usw. verbunden werden müssen. Möglicherweise auch in Menüs nützlich.

Eule
quelle
1
Wie definiert ist AFunc ein Zeiger auf die Elementfunktion, die zwei Ints nimmt und einen Vektor von Ints zurückgibt. Aber die Mitglieder zeigten darauf, int zurückzukehren, richtig? Ich denke, die typedef Anweisung sollte sein typedef int (A::*AFunc)(int I1,int I2);
riderBill
1

Obwohl Sie einen vorhandenen Elementfunktionszeiger leider nicht in einen einfachen Funktionszeiger konvertieren können, können Sie auf relativ einfache Weise eine Adapterfunktionsvorlage erstellen, die einen zur Kompilierungszeit bekannten Elementfunktionszeiger in eine normale Funktion wie diese umschließt:

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

Beachten Sie, dass zum Aufrufen der Member-Funktion eine Instanz von Abereitgestellt werden muss.

IllidanS4 will Monica zurück
quelle
Du hast mich inspiriert, @ IllidanS4. Siehe meine Antwort. +1
memtha
1

Aufbauend auf der Antwort von @ IllidanS4 habe ich eine Vorlagenklasse erstellt, mit der praktisch jede Mitgliedsfunktion mit vordefinierten Argumenten und Klasseninstanzen als Referenz für einen späteren Aufruf übergeben werden kann.



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 / Beispiel:

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
}

Dies funktioniert natürlich nur, wenn die angegebenen Argumente und die Eigentümerklasse noch gültig sind. Soweit Lesbarkeit ... bitte vergib mir.

Bearbeiten: Unnötiges Malloc entfernt, indem das Tupel normal gespeichert wird. Geerbter Typ für die Referenz hinzugefügt. Option hinzugefügt, um stattdessen alle Argumente zur Aufrufzeit bereitzustellen. Jetzt arbeite ich daran, beides zu haben ...

Edit 2: Wie versprochen, beide. Die einzige Einschränkung (die ich sehe) besteht darin, dass die vordefinierten Argumente vor den zur Laufzeit angegebenen Argumenten in der Rückruffunktion stehen müssen. Vielen Dank an @Chipster für die Hilfe bei der Einhaltung der gcc. Dies funktioniert auf gcc unter Ubuntu und Visual Studio unter 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
quelle