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 , appelez log() , mais sans que je sois obligé de saisir explicitement log () comme première ligne de chaque fonction? Je veux le faire, afin que je puisse append un comportement à chaque fonction sans avoir à passer par chaque fonction et m’assurer que l’appel est fait, et aussi que lorsque j’ajoute de nouvelles fonctions, le code est automatiquement ajouté …

Est-ce seulement possible ? Je ne peux pas imaginer comment faire cela avec les macros, donc je ne sais pas trop par où commencer … La seule façon dont j’ai pensé jusqu’ici, est d’append une “étape de pré-construction” pour parsingr le fichier avant de comstackr et éditez le code source, mais cela ne semble pas très intelligent ….

EDIT: Juste pour clarifier – je ne veux pas que log () se nomme évidemment. Il n’a pas besoin de faire partie de la classe.

EDIT: Je préférerais utiliser des méthodes qui fonctionneraient entre plates-formes, et en utilisant uniquement le stl.

Grâce aux propriétés inhabituelles d’ operator -> , nous pouvons injecter du code avant tout access à un membre, au désortingment d’une syntaxe légèrement courbée:

 // Nothing special in Foo struct Foo { void a() { } void b() { } void c() { } }; struct LoggingFoo : private Foo { void log() const { } // Here comes the sortingck Foo const *operator -> () const { log(); return this; } Foo *operator -> () { log(); return this; } }; 

L’utilisation se présente comme suit:

 LoggingFoo f; f->a(); 

Voir en direct sur Coliru

C’est une solution minimale (mais plutôt générale) au problème de l’ encapsuleur :

 #include  #include  template class CallProxy { T* p; C c{}; public: CallProxy(T* p) : p{p} {} T* operator->() { return p; } }; template class Wrapper { std::unique_ptr p; public: template Wrapper(Args&&... args) : p{std::make_unique(std::forward(args)...)} {} CallProxy operator->() { return CallProxy{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 w; w->foo(); } 

Définir une classe PrefixSuffix , avec le code de préfixe à l’ intérieur de son constructeur et le code de suffixe à l’intérieur du destructeur, est la voie à suivre. Ensuite, vous pouvez utiliser la classe Wrapper (en utilisant le -> pour accéder aux fonctions membres de votre classe d’origine) et le code de préfixe et de suffixe sera exécuté pour chaque appel.

Voyez-le vivre

Crédits à cet article , où j’ai trouvé la solution.


En guise de note: si la class qui doit être encapsulée ne possède pas virtual fonctions virtual , on pourrait déclarer la variable membre Wrapper::p non comme un pointeur, mais comme un object simple , puis un peu sur la sémantique de Wrapper opérateur de flèche ; le résultat est que vous n’auriez plus la charge de l’allocation dynamic de mémoire.

Vous pouvez faire un wrapper, quelque chose comme

 class Foo { public: void a() { /*...*/ } void b() { /*...*/ } }; class LogFoo { public: template  LogFoo(Ts&&... args) : foo(std::forward(args)...) {} const Foo* operator ->() const { log(); return &foo;} Foo* operator ->() { log(); return &foo;} private: void log() const {/*...*/} private: Foo foo; }; 

Et puis utilisez -> au lieu de . :

 LogFoo foo{/* args...*/}; foo->a(); foo->b(); 

Utilisez une expression lambda et une fonction d’ ordre supérieur pour éviter les répétitions et minimiser les risques d’oubli du log appels:

 class Foo { private: void log(const std::ssortingng&) { } template  void log_and_do(TF&& f, TArgs&&... xs) { log(std::forward(xs)...); std::forward(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 modifier log_and_do au lieu de changer chaque log appel de fonction si vous décidez de modifier le comportement de journalisation. Vous pouvez également transmettre n’importe quel nombre d’arguments supplémentaires pour vous log . Enfin, il devrait être optimisé par le compilateur – il se comportera comme si vous aviez écrit un appel pour vous log manuellement à chaque méthode.


Vous pouvez utiliser une macro (soupir) pour éviter un passe-partout:

 #define LOG_METHOD(...) \ __VA_ARGS__ \ { \ log_and_do([&] #define LOG_METHOD_END(...) \ , __VA_ARGS__); \ } 

Usage:

 class Foo { private: void log(const std::ssortingng&) { } template  void log_and_do(TF&& f, TArgs&&... xs) { log(std::forward(xs)...); std::forward(f)(); } public: LOG_METHOD(void a()) { // `a` implementation... } LOG_METHOD_END("Foo::a"); LOG_METHOD(void b()) { // `b` implementation... } LOG_METHOD_END("Foo::b"); }; 

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

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

 #include  class Foo { void log() {} public: template  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); } 

Est-il possible d’éviter le passe-partout?

Non.

C ++ a des capacités de génération de code très limitées, l’injection automatique de code n’en fait pas partie.


Clause de non-responsabilité: voici un aperçu de la fonction de proxy, qui demande à l’utilisateur de ne pas avoir ses pattes sales sur les fonctions qu’il ne doit pas appeler sans ignorer le proxy.

Est-il possible de faire oublier l’appel avant / après la fonction?

Faire respecter la délégation par le biais d’un proxy est … ennuyeux. Plus précisément, les fonctions ne peuvent pas être public ou protected , car sinon, l’appelant peut obtenir ses mains sales et vous pouvez déclarer qu’il est confisqué.

Une solution possible est donc de déclarer toutes les fonctions privées et de fournir des proxys qui imposent la journalisation. Abstraction faite de cela, pour que cette échelle couvre plusieurs classes, il est horriblement compliqué, bien que ce soit un coût ponctuel:

 template  class Applier { public: using Method = R (O::*)(Args...); constexpr explicit Applier(Method m): mMethod(m) {} R operator()(O& o, Args... args) const { o.pre_call(); R result = (o.*mMethod)(std::forward(args)...); o.post_call(); return result; } private: Method mMethod; }; template  class Applier { public: using Method = void (O::*)(Args...); constexpr explicit Applier(Method m): mMethod(m) {} void operator()(O& o, Args... args) const { o.pre_call(); (o.*mMethod)(std::forward(args)...); o.post_call(); } private: Method mMethod; }; template  class ConstApplier { public: using Method = R (O::*)(Args...) const; constexpr explicit ConstApplier(Method m): mMethod(m) {} R operator()(O const& o, Args... args) const { o.pre_call(); R result = (o.*mMethod)(std::forward(args)...); o.post_call(); return result; } private: Method mMethod; }; template  class ConstApplier { public: using Method = void (O::*)(Args...) const; constexpr explicit ConstApplier(Method m): mMethod(m) {} void operator()(O const& o, Args... args) const { o.pre_call(); (o.*mMethod)(std::forward(args)...); o.post_call(); } private: Method mMethod; }; 

Note: Je n’ai pas hâte d’append le support pour volatile , mais personne ne l’utilise, non?

Une fois ce premier obstacle franchi, vous pouvez utiliser:

 class MyClass { public: static const Applier a; static const ConstApplier b; void pre_call() const { std::cout < < "before\n"; } void post_call() const { std::cout << "after\n"; } private: void a_impl() { std::cout << "a_impl\n"; } int b_impl(int x) const { return mMember * x; } int mMember = 42; }; const Applier MyClass::a{&MyClass::a_impl}; const ConstApplier MyClass::b{&MyClass::b_impl}; 

C’est tout à fait la norme, mais au moins le schéma est clair, et toute violation sera comme un pouce endolori. Il est également plus facile d’appliquer les post-fonctions de cette manière, plutôt que de suivre chaque return .

La syntaxe à appeler n’est pas très bonne non plus:

 MyClass c; MyClass::a(c); std::cout < < MyClass::b(c, 2) << "\n"; 

Il devrait être possible de faire mieux ...


Notez que, idéalement, vous voudriez:

  • utiliser un membre de données
  • dont le type code le décalage à la classe (en toute sécurité)
  • dont le type code la méthode à appeler

Une solution à mi-chemin est (à mi-chemin car dangereux…):

 template  class Applier; template  class Applier { public: R operator()(Args... args) { O& o = *reinterpret_cast(reinterpret_cast(this) - N); o.pre_call(); R result = (o.*Method)(std::forward(args)...); o.post_call(); return result; } }; template  class Applier { public: void operator()(Args... args) { O& o = *reinterpret_cast(reinterpret_cast(this) - N); o.pre_call(); (o.*Method)(std::forward(args)...); o.post_call(); } }; template  class Applier { public: R operator()(Args... args) const { O const& o = *reinterpret_cast(reinterpret_cast(this) - N); o.pre_call(); R result = (o.*Method)(std::forward(args)...); o.post_call(); return result; } }; template  class Applier { public: void operator()(Args... args) const { O const& o = *reinterpret_cast(reinterpret_cast(this) - N); o.pre_call(); (o.*Method)(std::forward(args)...); o.post_call(); } }; 

Il ajoute un octet par "méthode" (parce que C ++ est bizarre comme ça), et nécessite des définitions assez impliquées:

 class MyClassImpl { friend class MyClass; public: void pre_call() const { std::cout < < "before\n"; } void post_call() const { std::cout << "after\n"; } private: void a_impl() { std::cout << "a_impl\n"; } int b_impl(int x) const { return mMember * x; } int mMember = 42; }; class MyClass: MyClassImpl { public: Applier a; Applier b; }; 

Mais au moins l'usage est "naturel":

 int main() { MyClass c; ca(); std::cout < < cb(2) << "\n"; return 0; } 

Personnellement, pour faire respecter cela, j'utiliserais simplement:

 class MyClass { public: void a() { log(); mImpl.a(); } int b(int i) const { log(); return mImpl.b(i); } private: struct Impl { public: void a_impl() { std::cout < < "a_impl\n"; } int b_impl(int x) const { return mMember * x; } private: int mMember = 42; } mImpl; }; 

Pas tout à fait extraordinaire, mais isoler simplement l'état dans MyClass::Impl rend difficile l'implémentation de la logique dans MyClass , ce qui est généralement suffisant pour garantir que les responsables suivent le modèle.