Was ist die praktische Verwendung von Zeigern auf Member-Funktionen?

Ich habe durchgelesenDieser Artikeenn Sie einen Zeiger auf eine Elementfunktion aufrufen möchten, benötigen Sie eine Instanz (entweder einen Zeiger auf eine oder eine Stapelreferenz), und nennen Sie sie folgendermaße

(instance.*mem_func_ptr)(..)
or
(instance->*mem_func_ptr)(..)

Meine Frage basiert auf diesem: Da Siehabe die Instanz, warum nicht die Member-Funktion direkt aufrufen, so:

instance.mem_func(..) //or: instance->mem_func(..)

Was ist die rationale / praktische Verwendung von Zeigern auf Member-Funktionen?

[bearbeiten

Ich spiele mit X-Development und habe das Stadium erreicht, in dem ich Widgets implementiere. Der Event-Loop-Thread zum Übersetzen der X-Ereignisse in meine Klassen und Widgets muss Threads für jedes Widget / Fenster starten, wenn ein Ereignis für sie eintrifft. Um dies richtig zu machen, dachte ich, ich brauche Funktionszeiger zu den Ereignishandlern in meinen Klassen.

Nicht so: Was ich entdeckt habe, war, dass ich dasselbe auf eine viel klarere und übersichtlichere Weise tun kann, indem ich einfach eine virtuelle Basisklasse benutze. Keine Notwendigkeit für Zeiger auf Member-Funktionen. Während der Entwicklung des oben Gesagten kam der Zweifel an der praktischen Verwendbarkeit / Bedeutung von Zeigern auf Member-Funktionen auf.

Die einfache Tatsache, dass Sie einen Verweis auf eine Instanz benötigen, um den Elementfunktionszeiger zu verwenden, macht die Notwendigkeit eines solchen überflüssig.

[Bearbeiten - @sbi & andere]

Hier ist ein Beispielprogramm, um meinen Standpunkt zu veranschaulichen: (Beachten Sie speziell 'Handle_THREE ()')

#include <iostream>
#include <string>
#include <map>


//-----------------------------------------------------------------------------
class Base
{
public:
    ~Base() {}
    virtual void Handler(std::string sItem) = 0;
};

//-----------------------------------------------------------------------------
typedef void (Base::*memfunc)(std::string);

//-----------------------------------------------------------------------------
class Paper : public Base
{
public:
    Paper() {}
    ~Paper() {}
    virtual void Handler(std::string sItem) { std::cout << "Handling paper\n"; }
};

//-----------------------------------------------------------------------------
class Wood : public Base
{
public:
    Wood() {}
    ~Wood() {}
    virtual void Handler(std::string sItem) { std::cout << "Handling wood\n"; }
};


//-----------------------------------------------------------------------------
class Glass : public Base
{
public:
    Glass() {}
    ~Glass() {}
    virtual void Handler(std::string sItem) { std::cout << "Handling glass\n"; }
};

//-----------------------------------------------------------------------------
std::map< std::string, memfunc > handlers;
void AddHandler(std::string sItem, memfunc f) { handlers[sItem] = f; }

//-----------------------------------------------------------------------------
std::map< Base*, memfunc > available_ONE;
void AddAvailable_ONE(Base *p, memfunc f) { available_ONE[p] = f; }

//-----------------------------------------------------------------------------
std::map< std::string, Base* > available_TWO;
void AddAvailable_TWO(std::string sItem, Base *p) { available_TWO[sItem] = p; }

//-----------------------------------------------------------------------------
void Handle_ONE(std::string sItem)
{
    memfunc f = handlers[sItem];
    if (f)
    {
        std::map< Base*, memfunc >::iterator it;
        Base *inst = NULL;
        for (it=available_ONE.begin(); ((it != available_ONE.end()) && (inst==NULL)); it++)
        {
            if (it->second == f) inst = it->first;
        }
        if (inst) (inst->*f)(sItem);
        else std::cout << "No instance of handler for: " << sItem << "\n";
    }
    else std::cout << "No handler for: " << sItem << "\n";
}

//-----------------------------------------------------------------------------
void Handle_TWO(std::string sItem)
{
    memfunc f = handlers[sItem];
    if (f)
    {
        Base *inst = available_TWO[sItem];
        if (inst) (inst->*f)(sItem);
        else std::cout << "No instance of handler for: " << sItem << "\n";
    }
    else std::cout << "No handler for: " << sItem << "\n";
}

//-----------------------------------------------------------------------------
void Handle_THREE(std::string sItem)
{
    Base *inst = available_TWO[sItem];
    if (inst) inst->Handler(sItem);
    else std::cout << "No handler for: " << sItem << "\n";
}


//-----------------------------------------------------------------------------
int main()
{
    Paper p;
    Wood w;
    Glass g;


    AddHandler("Paper", (memfunc)(&Paper::Handler));
    AddHandler("Wood", (memfunc)(&Wood::Handler));
    AddHandler("Glass", (memfunc)(&Glass::Handler));

    AddAvailable_ONE(&p, (memfunc)(&Paper::Handler));
    AddAvailable_ONE(&g, (memfunc)(&Glass::Handler));

    AddAvailable_TWO("Paper", &p);
    AddAvailable_TWO("Glass", &g);

    std::cout << "\nONE: (bug due to member-function address being relative to instance address)\n";
    Handle_ONE("Paper");
    Handle_ONE("Wood");
    Handle_ONE("Glass");
    Handle_ONE("Iron");

    std::cout << "\nTWO:\n";
    Handle_TWO("Paper");
    Handle_TWO("Wood");
    Handle_TWO("Glass");
    Handle_TWO("Iron");

    std::cout << "\nTHREE:\n";
    Handle_THREE("Paper");
    Handle_THREE("Wood");
    Handle_THREE("Glass");
    Handle_THREE("Iron");
}

{edit]Potentialproblem mit Direktaufruf in obigem Beispiel:
In Handler_THREE () muss der Name der Methode fest codiert sein, damit Änderungen an der Stelle vorgenommen werden können, an der sie verwendet wird, um Änderungen an der Methode zu übernehmen. Wenn Sie einen Zeiger auf die Elementfunktion verwenden, müssen Sie nur die Position ändern, an der der Zeiger erstellt wird.

[edit]Praktische Verwendungen aus den Antworten entnommen:

Vonanswer von Chubsdad:
What: Eine dedizierte 'Caller'-Funktion wird verwendet, um die mem-func-ptr aufzurufen;
Benefit: So schützen Sie Code mithilfe von Funktionen, die von anderen Objekten bereitgestellt werden
How: Wenn die jeweilige (n) Funktion (en) an vielen Stellen verwendet werden und sich der Name und / oder die Parameter ändern, müssen Sie nur den Namen ändern, unter dem sie als Zeiger zugewiesen sind, und den Aufruf in der 'Anrufer'-Funktion anpassen . (Wenn die Funktion als instance.function () verwendet wird, muss sie überall geändert werden.)

Vonanswer von Matthew Flaschen:
What: Lokale Spezialisierung in einer Klasse
Benefit: Macht den Code viel klarer, einfacher und einfacher zu verwenden und zu warten
How: Ersetzt Code, der normalerweise mithilfe komplexer Logik implementiert wird, durch (potenziell) große switch () / if-then-Anweisungen mit direkten Zeigern auf die Spezialisierung. ziemlich ähnlich wie die 'Anrufer'-Funktion oben.

Antworten auf die Frage(26)

Ihre Antwort auf die Frage