web-dev-qa-db-fra.com

Appel de méthodes de classe C ++ via un pointeur de fonction

Comment puis-je obtenir un pointeur de fonction pour une fonction de membre de classe et ensuite appeler cette fonction de membre avec un objet spécifique? Je voudrais écrire:

class Dog : Animal
{
    Dog ();
    void bark ();
}

…
Dog* pDog = new Dog ();
BarkFunction pBark = &Dog::bark;
(*pBark) (pDog);
…

De plus, si possible, j'aimerais également appeler le constructeur via un pointeur:

NewAnimalFunction pNew = &Dog::Dog;
Animal* pAnimal = (*pNew)();    

Est-ce possible, et si oui, quel est le moyen préféré de le faire?

100
Tony the Pony

Lire this pour plus de détails:

// 1 define a function pointer and initialize to NULL

int (TMyClass::*pt2ConstMember)(float, char, char) const = NULL;

// C++

class TMyClass
{
public:
   int DoIt(float a, char b, char c){ cout << "TMyClass::DoIt"<< endl; return a+b+c;};
   int DoMore(float a, char b, char c) const
         { cout << "TMyClass::DoMore" << endl; return a-b+c; };

   /* more of TMyClass */
};
pt2ConstMember = &TMyClass::DoIt; // note: <pt2Member> may also legally point to &DoMore

// Calling Function using Function Pointer

(*this.*pt2ConstMember)(12, 'a', 'b');
110
Satbir

Comment puis-je obtenir un pointeur de fonction pour une fonction de membre de classe et ensuite appeler cette fonction de membre avec un objet spécifique?

Il est plus facile de commencer avec un typedef. Pour une fonction membre, vous ajoutez le nom de la classe dans la déclaration de type:

typedef void(Dog::*BarkFunction)(void);

Ensuite, pour appeler la méthode, utilisez l'opérateur ->*:

(pDog->*pBark)();

De plus, si possible, j'aimerais également appeler le constructeur via un pointeur. Est-ce possible et, dans l'affirmative, quelle est la meilleure façon de procéder?

Je ne crois pas que vous puissiez travailler avec des constructeurs comme celui-ci - les ctors et les dtors sont spéciaux. Le moyen habituel de réaliser ce type de chose serait d'utiliser une méthode factory, qui est simplement une fonction statique qui appelle le constructeur pour vous. Voir le code ci-dessous pour un exemple.

J'ai modifié votre code pour faire fondamentalement ce que vous décrivez. Il y a quelques mises en garde ci-dessous.

#include <iostream>

class Animal
{
public:

    typedef Animal*(*NewAnimalFunction)(void);

    virtual void makeNoise()
    {
        std::cout << "M00f!" << std::endl;
    }
};

class Dog : public Animal
{
public:

    typedef void(Dog::*BarkFunction)(void);

    typedef Dog*(*NewDogFunction)(void);

    Dog () {}

    static Dog* newDog()
    {
        return new Dog;
    }

    virtual void makeNoise ()
    {
        std::cout << "Woof!" << std::endl;
    }
};

int main(int argc, char* argv[])
{
    // Call member function via method pointer
    Dog* pDog = new Dog ();
    Dog::BarkFunction pBark = &Dog::makeNoise;

    (pDog->*pBark)();

    // Construct instance via factory method
    Dog::NewDogFunction pNew = &Dog::newDog;

    Animal* pAnimal = (*pNew)();

    pAnimal->makeNoise();

    return 0;
}

Maintenant, bien que vous puissiez normalement utiliser un Dog* à la place d'un Animal* grâce à la magie du polymorphisme, le type d'un pointeur de fonction ne n'est pas suit les règles de recherche de la hiérarchie de classes. Ainsi, un pointeur de méthode Animal n'est pas compatible avec un pointeur de méthode Dog. En d'autres termes, vous ne pouvez pas affecter un Dog* (*)() à une variable de type Animal* (*)().

La méthode statique newDog est un exemple simple de fabrique, qui crée et retourne simplement de nouvelles instances. Étant une fonction statique, elle a un typedef régulier (sans qualificateur de classe).

Ayant répondu à ce qui précède, je me demande s’il n’existe pas de meilleur moyen de réaliser ce dont vous avez besoin. Il existe quelques scénarios spécifiques dans lesquels vous feriez ce genre de chose, mais vous constaterez peut-être que d'autres modèles fonctionnent mieux pour votre problème. Si vous décrivez en termes plus généraux ce que vous essayez d'atteindre, l'esprit de ruche peut s'avérer encore plus utile!

En rapport avec ce qui précède, vous trouverez sans aucun doute la bibliothèque Boost bind et d’autres modules connexes très utiles.

54
gavinb

Je ne pense pas que quiconque ait expliqué ici que l'un des problèmes est qu'il vous faut " pointeurs membres " plutôt que des pointeurs de fonction normaux.

Les pointeurs de membre vers des fonctions ne sont pas simplement des pointeurs de fonction. En termes d’implémentation, le compilateur ne peut pas utiliser une adresse de fonction simple car, en général, vous ne connaissez pas l’adresse à appeler avant de savoir pour quel objet déréférencer (pensez aux fonctions virtuelles). Vous devez également connaître l'objet pour pouvoir fournir le paramètre implicite this, bien entendu.

Ayant dit que vous en avez besoin, je dirai maintenant que vous devez vraiment les éviter. Sérieusement, les pointeurs de membre sont une douleur. Il est beaucoup plus sain de regarder des modèles de conception orientés objet qui atteignent le même objectif, ou d'utiliser un boost::function ou peu importe ce qui est mentionné ci-dessus - en supposant que vous ayez à faire ce choix.

Si vous fournissez ce pointeur de fonction au code existant, vous devez donc réellement besoin un simple pointeur de fonction, vous devez écrire une fonction en tant que membre statique de la classe. Une fonction membre statique ne comprend pas this, vous devez donc passer l'objet en tant que paramètre explicite. Il y avait une fois un idiome pas-cela-inhabituel le long de ces lignes pour travailler avec l'ancien code C qui a besoin de pointeurs de fonction

class myclass
{
  public:
    virtual void myrealmethod () = 0;

    static void myfunction (myclass *p);
}

void myclass::myfunction (myclass *p)
{
  p->myrealmethod ();
}

Puisque myfunction n'est en réalité qu'une fonction normale (à part les problèmes de portée), un pointeur de fonction peut être trouvé dans le mode C normal.

EDIT - ce genre de méthode s'appelle une "méthode de classe" ou une "fonction membre statique". La principale différence par rapport à une fonction non membre est que, si vous la référencez de l'extérieur de la classe, vous devez spécifier la portée à l'aide de l'opérateur de résolution de portée ::. Par exemple, pour obtenir le pointeur de fonction, utilisez &myclass::myfunction et pour l'appeler, utilisez myclass::myfunction (arg);.

Ce genre de chose est assez courant lorsque vous utilisez les anciennes API Win32, conçues à l'origine pour le C plutôt que pour le C++. Bien sûr, dans ce cas, le paramètre est normalement LPARAM ou similaire plutôt qu'un pointeur, et un transtypage est nécessaire.

31
Steve314
typedef void (Dog::*memfun)();
memfun doSomething = &Dog::bark;
....
(pDog->*doSomething)(); // if pDog is a pointer
// (pDog.*doSomething)(); // if pDog is a reference
18
AraK

Je suis venu ici pour apprendre à créer un pointeur de fonction (pas un pointeur de méthode) à partir d'une méthode, mais aucune des réponses fournies ici ne fournit de solution. J'ai donc réfléchi et trouvé une solution intéressante qui mérite d'être partagée:

template <class T> struct MethodHelper;
template <class C, class Ret, class... Args> struct MethodHelper<Ret(C::*)(Args...)> {
    using T = Ret (C::*)(Args...);
    template <T m> static Ret call(C* object, Args... args) {
        return (object->*m)(args...);
    }
};

#define METHOD_FP(m) MethodHelper<decltype(m)>::call<m>

Donc, pour votre exemple, vous feriez maintenant:

Dog dog;
using BarkFunction = void (*)(Dog*);
BarkFunction bark = METHOD_FP(&Dog::bark);
(*bark)(&dog); // or simply bark(&dog)
9
eyelash

Exemple minimal exécutable

#include <cassert>

class C {
    public:
        int i;
        C(int i) : i(i) {}
        int m(int j) { return this->i + j; }
};

int main() {
    // Get a method pointer.
    int (C::*p)(int) = &C::m;

    // Create a test object.
    C c(1);
    C *cp = &c;

    // Operator .*
    assert((c.*p)(2) == 3);

    // Operator ->*
    assert((cp->*p)(2) == 3);
}

Vous ne pouvez pas changer l'ordre des parenthèses ou les omettre. Les suivants ne fonctionnent pas:

c.*p(2)
c.*(p)(2)

C++ 11 standard

.* et ->* sont des opérateurs singe introduits en C++ à cette fin et non présents en C.

projet de norme C++ 11 N3337 :

  • 2.13 "Opérateurs et ponctuateurs" a une liste de tous les opérateurs, qui contient .* et ->*.
  • 5.5 "Les opérateurs pointeurs vers les membres" explique leur travail

Un pointeur de fonction sur un membre de la classe est un problème qui convient vraiment à l’utilisation de boost :: function. Petit exemple:

#include <boost/function.hpp>
#include <iostream>

class Dog 
{
public:
   Dog (int i) : tmp(i) {}
   void bark ()
   {
      std::cout << "woof: " << tmp << std::endl;
   }
private:
   int tmp;
};



int main()
{
   Dog* pDog1 = new Dog (1);
   Dog* pDog2 = new Dog (2);

   //BarkFunction pBark = &Dog::bark;
   boost::function<void (Dog*)> f1 = &Dog::bark;

   f1(pDog1);
   f1(pDog2);
}
6
Benjamin

La raison pour laquelle vous ne pouvez pas utiliser les pointeurs de fonction pour appeler des fonctions membres est que les pointeurs de fonction ordinaires ne sont généralement que l'adresse de mémoire de la fonction.

Pour appeler une fonction membre, vous devez connaître deux choses:

  • Quelle fonction membre appeler
  • Quelle instance doit être utilisée (dont la fonction membre)

Les pointeurs de fonction ordinaires ne peuvent pas stocker les deux. Les pointeurs de fonction de membre C++ sont utilisés pour stocker a). C'est pourquoi vous devez spécifier explicitement l'instance lorsque vous appelez un pointeur de fonction membre.

6
hrnt

Pour créer un nouvel objet, vous pouvez soit utiliser placement new, comme indiqué ci-dessus, ou bien demander à votre classe d'implémenter une méthode clone () qui crée une copie de l'objet. Vous pouvez ensuite appeler cette méthode de clonage à l'aide d'un pointeur de fonction membre, comme expliqué ci-dessus, pour créer de nouvelles instances de l'objet. L'avantage du clone est que vous pouvez parfois utiliser un pointeur sur une classe de base sans connaître le type de l'objet. Dans ce cas, une méthode clone () peut être plus facile à utiliser. Clone () vous permettra également de copier l'état de l'objet si c'est ce que vous voulez.

2
Corwin Joy