2 votes

Existe-t-il un moyen de déclarer un pointeur vers une méthode particulière uniquement ?

J'ai une classe :

class Car
{
    public:
        Car();
        ~Car();
        // nr indicates which particular door.
        void startEngie();
        void openDoor(int nr);
        void closeDoor(int nr); 
        void openWindow(int nr);
        void closeWindow(int nr);
        void turnOnAirConditioner(int vloume);
}

Maintenant par void (Car::*action)(int) Je déclare un pointeur nommé action a Car méthode. Le problème est que de nombreuses méthodes peuvent lui être attribuées : &Car::openDoor , &Car::closeDoor , &Car::openWindow , &Car::closeWindow , &Car::turnOnAirConditioner mais pas &Car::startEngie .

Je me demande si j'ai raison : il n'y a pas moyen de déclarer un pointeur sur une méthode particulière uniquement. En d'autres termes : pointer type qui n'acceptent qu'une fonction nommée particulière, ne peuvent pas être déclarées.

3voto

molbdnilo Points 9289

Vous ne pouvez pas définir un type qui es juste "une valeur spécifique de cet autre type", par exemple un type dont la seule valeur existante est exactement &Car::closeDoor .

Une chose que vous peut est d'utiliser le couteau suisse de la programmation - introduire un niveau d'indirection - et de définir un type qui représente une valeur spécifique en l'enveloppant dans quelque chose d'autre.

// Simplified Car
class Car
{
public:
    void openDoor(int i) { std::cout << "open door\n"; }
    void closeWindow(int i) { std:cout << "close window\n"; }
};

using Fun = void (Car::*)(int);

// A template for an immutable structure, so it 
// can hold only one specific value; the template argument.
template<Fun fun>
struct CarOperation
{
    const Fun f = fun;
};

// Now we can make one type for each operation
using OpenDoor = CarOperation<&Car::openDoor>;
using CloseWindow = CarOperation<&Car::closeWindow>;

int main()
{
    Car c;
    OpenDoor d;
    (c.*d.f)(1);
    CloseWindow e;
    (c.*e.f)(2);
}

La syntaxe est un peu malheureuse, car apparemment l'option .* n'effectue aucune conversion implicite de son opérande de droite.

Bien entendu, il s'agit d'une manière très détournée et obscurcie d'appeler simplement la fonction indiquée par le type.

Il serait plus utile d'utiliser un type pouvant contenir un sous-ensemble prédéterminé de fonctions (comme, par exemple, le type DoorOperation o Opener ).
Vous pouvez probablement construire cela en utilisant une programmation astucieuse des modèles, mais je ne suis pas un programmeur de modèles astucieux.

1voto

Marek R Points 4503

Ce type de pointeurs void (Car::*action)(int) sont rarement utilisés. Aujourd'hui, il existe trois méthodes préférées pour aborder cette question :

  • paramètre du modèle (algorithmes STL par exemple)
  • interface (classe abstraite pure)
  • std::function<void()> et ou lambdas

Apparemment, vous avez besoin d'une deuxième solution :

class IWindowOpenable {
public:
    virtual ~IWindowOpenable() {}

    virtual void openWindow(int nr) = 0;
};

class Car : public IWindowOpenable
{
public:
    Car();
    ~Car();

    void startEngie();
    void openDoor(int nr);
    void closeDoor(int nr); 
    void openWindow(int nr);
    void closeWindow(int nr);
    void turnOnAirConditioner(int vloume);
};

Car car;
IWindowOpenable *windowOpener = &Car;

Notez que tout ce qui met en œuvre l'interface IWindowOpenable ne peut opérer que sur une méthode portant le nom openWindow . Ce pointeur peut pointer vers n'importe quel type d'objet implémentant cette interface.

1voto

Evg Points 4011

Dans les commentaires, vous avez dit que vous posiez une question purement académique, alors permettez-moi de vous donner une réponse purement académique. Vous ne pouvez pas avoir un pointeur sur une seule méthode, parce que toutes ces méthodes ont le même type. Ce que vous pouvez faire, c'est leur donner des types différents :

struct Car
{
    struct open_door_tag { };
    struct close_door_tag { };

    void open_left_door(int nr, open_door_tag = {});
    void open_right_door(int nr, open_door_tag = {});
    void close_door(int nr, close_door_tag = {});
};

Maintenant, si vous avez un pointeur

void (Car::*open_that_door_ptr)(int, Car::open_door_tag);

vous avez des restrictions en matière d'affectation :

open_that_door_ptr = &Car::open_left_door;   // OK
open_that_door_ptr = &Car::open_right_door;  // OK
//open_that_door_ptr = &Car::close_door;     // Won't compile

Malheureusement (ou non), il n'est pas possible d'appeler une méthode par l'intermédiaire de ce pointeur sans fournir une balise :

Car car;
(car.*open_that_door_ptr)(2, Car::open_door_tag{});
// or (thanks, @Jarod42)
(car.*open_that_door_ptr)(2, {});
// (car.*open_that_door_ptr)(2);     // Won't compile

Remarque. Après avoir posté cette réponse, j'ai vu un commentaire de @Jarod42, qui suggérait de faire essentiellement la même chose.

1voto

Peter Schneider Points 1913

Malgré le conseil judicieux de Marek d'envisager l'utilisation d'alternatives plus idiomatiques au C++, votre souhait est raisonnable et peut être exaucé par un simple const .

La raison pour laquelle on peut vouloir faire cela est la même que pour toutes les autres définitions de const (qui peuvent toutes être remplacées par le côté droit de leur définition) : On veut avoir un nom symbolique pour que la signification de la valeur spécifique soit compréhensible dans le contexte de son utilisation, et on veut avoir un point de maintenance unique au cas où l'on voudrait changer la valeur de const valeur. C'est la raison pour laquelle tout le monde désapprouve les nombres entiers dans le code.

Ces mêmes raisons s'appliquent à la définition de const int et void (Car::*const action)(int) s similaires.

Ainsi, si, pour donner un exemple artificiel, vous voulez une fonction qui est appelée chaque fois que la voiture est démarrée, en tant que propriété non modifiable de cette voiture spécifique, vous pouvez déclarer un fichier atStart Le pointeur de fonction d'un membre constant en tant que membre (j'ai également introduit un typedef). Le pointeur doit être initialisé dans le constructeur de la voiture, comme tous les membres constants.

#include<iostream>
using namespace std;

struct Car
{
        typedef void (Car::* const CarfuncT)(void);
        Car(CarfuncT atStartArg)
            : atStart(atStartArg) 
        {}
        void startEngine() { cout << "Start Engine\n"; (this->*atStart)(); };
        void turnOnAC() { cout << "AC on\n"; }
        void turnOnHeat() { cout << "Heat on\n"; }
        void turnOnRadio() { cout << "Radio on\n"; }
        void (Car::* const atStart)();
};

Le code utilisateur pourrait ressembler à ceci :

int main()
{
  cout << "I'll create a configured car. In which state do you live?\n";

  string state;
  cin >> state;
  Car *car = new Car(state == "Texas" ? &Car::turnOnAC : &Car::turnOnRadio);

  car->startEngine();

  delete car;
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X