74 votes

Comment s'assurer que chaque méthode d'une classe appelle d'abord une autre méthode ?

J'ai :

class Foo {
   public:
      void log() { }

      void a() {
         log();
      }

      void b() {
         log();
      }
};

Existe-t-il un moyen d'avoir chaque méthode de Foo appel log() mais sans que je doive explicitement taper log() comme première ligne de chaque fonction ? Je veux faire cela, afin de pouvoir ajouter un comportement à chaque fonction sans devoir passer par chaque fonction et m'assurer que l'appel est fait, et aussi pour que lorsque j'ajoute de nouvelles fonctions, le code soit automatiquement ajouté...

Est-ce possible ? Je n'arrive pas à imaginer comment le faire avec des macros, donc je ne sais pas par où commencer... Le seul moyen auquel j'ai pensé jusqu'à présent est d'ajouter une "étape de pré-construction", de sorte qu'avant de compiler, je scanne le fichier et modifie le code source, mais cela ne semble pas très intelligent.....

EDIT : Juste pour clarifier - je ne veux pas que log() s'appelle lui-même évidemment. Il n'a pas besoin de faire partie de la classe.

EDIT : Je préférerais utiliser des méthodes qui fonctionnent sur plusieurs plateformes, et n'utiliser que le stl.

110voto

Quentin Points 3904

Grâce aux propriétés inhabituelles des operator -> nous pouvons injecter du code avant tout l'accès des membres, au prix d'une syntaxe un peu tordue :

// Nothing special in Foo
struct Foo {
    void a() { }
    void b() { }
    void c() { }
};

struct LoggingFoo : private Foo {
    void log() const { }

    // Here comes the trick
    Foo const *operator -> () const { log(); return this; }
    Foo       *operator -> ()       { log(); return this; }
};

L'utilisation se présente comme suit :

LoggingFoo f;
f->a();

Voyez-le en direct sur Coliru

37voto

Paolo M Points 584

Il s'agit d'une solution minimale (mais assez générale) à la question de l'accès à l'information. problème de l'enveloppe :

#include <iostream>
#include <memory>

template<typename T, typename C>
class CallProxy {
    T* p;
    C c{};
public:
    CallProxy(T* p) : p{p} {}
    T* operator->() { return p; } 
};

template<typename T, typename C>
class Wrapper {
    std::unique_ptr<T> p;
public:
    template<typename... Args>
    Wrapper(Args&&... args) : p{std::make_unique<T>(std::forward<Args>(args)...)} {}
    CallProxy<T, C> operator->() { return CallProxy<T, C>{p.get()}; } 
};

struct PrefixSuffix {
    PrefixSuffix() { std::cout << "prefix\n"; }
    ~PrefixSuffix() { std::cout << "suffix\n"; }
};

struct MyClass {
    void foo() { std::cout << "foo\n"; }
};

int main()
{
    Wrapper<MyClass, PrefixSuffix> w;
    w->foo();
}

Définir un PrefixSuffix avec la classe code préfixe dans son constructeur et l'élément code suffixe à l'intérieur du destructeur est la meilleure solution. Ensuite, vous pouvez utiliser le Wrapper (en utilisant la classe -> pour accéder aux fonctions membres de votre classe originale) et le code préfixe et suffixe sera exécuté pour chaque appel.

Voir en direct .

Crédits pour cet article où j'ai trouvé la solution.


À titre d'information, si le class qui doit être emballé n'a pas virtual on pourrait déclarer la fonction Wrapper::p non pas en tant que pointeur, mais en tant que simple objet puis de faire un peu de piratage sur la sémantique de Wrapper 's opérateur de flèche le résultat est que vous n'auriez plus la surcharge de l'allocation dynamique de la mémoire.

17voto

Jarod42 Points 15729

Vous pouvez faire une enveloppe, quelque chose comme

class Foo {
public:
    void a() { /*...*/ }
    void b() { /*...*/ }
};

class LogFoo
{
public:
    template <typename ... Ts>
    LogFoo(Ts&&... args) : foo(std::forward<Ts>(args)...) {}

    const Foo* operator ->() const { log(); return &foo;}
    Foo* operator ->() { log(); return &foo;}
private:
    void log() const {/*...*/}
private:
    Foo foo;
};

Et ensuite utiliser -> au lieu de . :

LogFoo foo{/* args...*/};

foo->a();
foo->b();

9voto

Vittorio Romeo Points 2559

Utilisez un expression lambda et un ordre supérieur pour éviter les répétitions et minimiser les risques d'oublier d'appeler la fonction log :

class Foo
{
private:
    void log(const std::string&)
    {

    }

    template <typename TF, typename... TArgs>
    void log_and_do(TF&& f, TArgs&&... xs)
    {
        log(std::forward<TArgs>(xs)...);
        std::forward<TF>(f)();
    }

public:
    void a()
    {
        log_and_do([this]
        {
            // `a` implementation...
        }, "Foo::a");
    }

    void b()
    {
        log_and_do([this]
        {
            // `b` implementation...
        }, "Foo::b");
    }
};

L'avantage de cette approche est que vous pouvez changer log_and_do au lieu de modifier chaque fonction appelant log si vous décidez de modifier le comportement de journalisation. Vous pouvez également passer un nombre quelconque d'arguments supplémentaires à log . Enfin, il devrait être optimisé par le compilateur - il se comportera comme si vous aviez écrit un appel à log manuellement dans toutes les méthodes.


Vous pouvez utiliser une macro (soupir) afin d'éviter de recourir à un langage passe-partout :

#define LOG_METHOD(...) \
    __VA_ARGS__ \
    { \
        log_and_do([&]

#define LOG_METHOD_END(...) \
        , __VA_ARGS__); \
    }

Utilisation :

class Foo
{
private:
    void log(const std::string&)
    {

    }

    template <typename TF, typename... TArgs>
    void log_and_do(TF&& f, TArgs&&... xs)
    {
        log(std::forward<TArgs>(xs)...);
        std::forward<TF>(f)();
    }

public:
    LOG_METHOD(void a())
    {
        // `a` implementation...
    }
    LOG_METHOD_END("Foo::a");

    LOG_METHOD(void b())
    {
        // `b` implementation...
    }
    LOG_METHOD_END("Foo::b");
};

9voto

gabry Points 56

Je suis d'accord sur ce qui est écrit dans les commentaires de vos posts originaux, mais si vous avez vraiment besoin de faire cela et que vous n'aimez pas utiliser une macro C, vous pouvez ajouter une méthode pour appeler vos méthodes.

Voici un exemple complet utilisant C++ 2011 pour gérer correctement les paramètres de fonction variables. Testé avec GCC et clang

#include <iostream>

class Foo
{
        void log() {}
    public:
        template <typename R, typename... TArgs>        
        R call(R (Foo::*f)(TArgs...), const TArgs... args) {
            this->log();
            return (this->*f)(args...);
        }

        void a() { std::cerr << "A!\n"; }
        void b(int i) { std::cerr << "B:" << i << "\n"; }
        int c(const char *c, int i ) { std::cerr << "C:" << c << '/' << i << "\n"; return 0; }
};

int main() {
    Foo c;

    c.call(&Foo::a);
    c.call(&Foo::b, 1);
    return c.call(&Foo::c, "Hello", 2);
}

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