Question

J'ai une classe qui a un vecteur d'un autre objet de classe en tant que membre. Dans de nombreuses fonctions de cette classe, je dois effectuer la même opération sur tous les objets du vecteur:

class Small
{
  public:
    void foo(); 
    void bar(int x);
    // and many more functions
};

class Big
{
  public:
    void foo()
    {
        for (size_t i = 0; i <  VectorOfSmalls.size(); i++)
            VectorOfSmalls[i]->foo();
    }
    void bar(int x)
    {
        for (size_t i = 0; i <  VectorOfSmalls.size(); i++)
            VectorOfSmalls[i]->bar(x);
    }
    // and many more functions
  private:
    vector<Small*> VectorOfSmalls;
};

Je souhaite simplifier le code et trouver un moyen de ne pas dupliquer le vecteur dans toutes les fonctions.

J'ai envisagé de créer une fonction qui reçoit un pointeur sur une fonction et appelle la fonction indiquée sur chaque membre d'un vecteur. Mais je ne suis pas sûr que l’utilisation de pointeurs vers des fonctions en C ++ soit une bonne idée.

J'ai également pensé aux foncteurs et fonctionnoides , mais cela me forcera à créer une classe pour chaque fonction et cela ressemblera à une overkill.

Une autre solution possible consiste à créer une fonction qui reçoit une chaîne et appelle la commande en fonction de la chaîne:

void Big::call_command(const string & command)
{
    for (size_t i = 0; i <  VectorOfSmalls.size(); i++)
    {
       if (command == "foo")
           VectorOfSmalls[i]->foo();
       else if (command == "bar")
           VectorOfSmalls[i]->bar();
    }
}
void Big::foo()
{
    call_command("foo");
}

Mais cela pourrait fonctionner lentement (création inutile d'une chaîne au lieu d'un simple appel de fonction) et créer un problème si les fonctions ont une signature différente.

Alors, que recommanderiez-vous? Est-ce que je devrais tout laisser comme avant?

EDIT: je ne peux utiliser que STL et pas boost (anciens compilateurs).

Était-ce utile?

La solution

Eh bien, vous pouvez réécrire les boucles for pour utiliser des itérateurs et davantage de la STL comme ceci:

void foo() {
    std::for_each(VectorOfSmalls.begin(), VectorOfSmalls.end(), std::mem_fun(&Small::foo));
}

void bar() {
    std::for_each(VectorOfSmalls.begin(), VectorOfSmalls.end(), std::mem_fun(&Small::bar));
}

Au-delà de cela, vous pouvez utiliser des macros pour éviter de retaper cela souvent, mais je ne suis pas un grand fan de cela. Personnellement, j'aime les fonctions multiples par rapport à la seule qui prend une chaîne de commande. Cela vous donne plus de souplesse quant à la manière dont la décision est prise.

Si vous n'utilisez qu'une seule fonction en prenant un paramètre pour décider laquelle faire, j'utiliserais une énumération et un commutateur comme celui-ci, ce serait plus efficace que des chaînes et un if en cascade. En outre, dans votre exemple, vous devez décider si vous voulez faire partie de la boucle. Il est plus efficace de vérifier en dehors de la boucle et d’avoir des copies redondantes de la boucle car "quelle commande" doit être décidé une fois par appel. (REMARQUE: vous pouvez faire de la commande un paramètre de modèle si elle est connue au moment de la compilation, ce qui semble être le cas.)

class Big {
public:
    enum Command {
        DO_FOO,
        DO_BAR
    };

void doit(Command cmd) {
    switch(cmd) {
    case DO_FOO:
        std::for_each(VectorOfSmalls.begin(), VectorOfSmalls.end(), std::mem_fun(&Small::foo));
        break;
    case DO_BAR:
        std::for_each(VectorOfSmalls.begin(), VectorOfSmalls.end(), std::mem_fun(&Small::bar));
        break;
    }
};

En outre, comme vous l'avez mentionné, il est assez simple de remplacer le pointeur & Small :: quel que soit le pointeur de la fonction membre et de simplement le passer en tant que paramètre. Vous pouvez même en faire un modèle également.

class Big {
public:
    template<void (Small::*fn)()>
    void doit() {
        std::for_each(VectorOfSmalls.begin(), VectorOfSmalls.end(), std::mem_fun(fn));
    }
};

Ensuite, vous pouvez faire:

Big b;
b.doit<&Small::foo>();
b.doit<&Small::bar>();

La bonne chose à propos de cette méthode et des méthodes de paramètres habituelles est que Big n'a pas besoin d'être modifié si vous changez petit pour avoir plus de routines! Je pense que c'est la méthode préférée.

Si vous souhaitez gérer un seul paramètre, vous devez également ajouter un bind2nd. Voici un exemple complet:

#include <algorithm>
#include <functional>
#include <iostream>
#include <vector>

class Small {
public:
    void foo() { std::cout << "foo" << std::endl; }
    void bar(int x) { std::cout << "bar" << std::endl; }
};


class Big {
public:
    template<void (Small::*fn)()>
    void doit() {
        std::for_each(VectorOfSmalls.begin(), VectorOfSmalls.end(), std::mem_fun(fn));
    }

    template<class T, void (Small::*fn)(T)>
    void doit(T x) {
        std::for_each(VectorOfSmalls.begin(), VectorOfSmalls.end(), std::bind2nd(std::mem_fun(fn), x));
    }
public:
    std::vector<Small *> VectorOfSmalls;
};

int main() {
    Big b;
    b.VectorOfSmalls.push_back(new Small);
    b.VectorOfSmalls.push_back(new Small);

    b.doit<&Small::foo>();
    b.doit<int, &Small::bar>(5);
}

Autres conseils

Si vous utilisez la bibliothèque std, consultez pour_each .

Vous avez mentionné que l’utilisation de pointeurs de fonction en C ++ n’était peut-être pas une bonne idée. Cependant, si vous vous inquiétez, c’est rapide, vous devez voir s’il s’agit d’un obstacle dans la performance, avant de vous inquiéter.

Essayez boost :: function et < un href = "http://www.boost.org/doc/libs/1_37_0/libs/bind/bind.html" rel = "aucune erreur"> boost :: bind :

void Big::call_command(const boost::function<void (Small*)>& f)
{
    for (size_t i = 0; i <  VectorOfSmalls.size(); i++)
    {
        f(VectorOfSmalls[i]);
    }
}

int main()
{
    Big b;
    b.call_command(boost::bind(&Small::foo, _1));
    b.call_command(boost::bind(&Small::bar, _1, 5));
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top