11
votes

Comment mettre en œuvre des décorateurs en C et C ++

J'ai une situation en C ainsi que dans C ++ qui peut être mieux résolue avec quelque chose comme le Python comme des décorateurs: j'ai peu de fonctions que j'aimerais envelopper avec autre chose pour que la fonction ne pénètre Les déclarations sont effectuées et quand il laisse une autre fonctionnalité est exécutée.

Par exemple, j'ai quelques fonctions dans un fichier de bibliothèque C qui, lorsque cela est appelé doit verrouiller un sémaphore et avant de renvoyer le contrôle sur Callee, devrait libérer le sémaphore. Sans la serrure, ils ont suivi la structure: xxx

Je voudrais définir un sémaphore global qui doit être verrouillé avant que chacune de ces fonctions soit appelée et libérée lorsque la fonction est renvoyée. . Je voudrais le faire avec autant de simplicité que possible; Quelque chose proche de ceci: xxx

ou quelque chose comme xxx

Ce que je ne veux pas est:

  1. Modifiez les noms de fonctions car ils sont des fonctions de la bibliothèque et sont appelés de nombreux endroits.
  2. Placez explicitement une déclaration avant le retour des appels, car la plupart des fonctions ont de nombreux retours précoces entre les deux. Ou pour cette affaire, changez tout interne de la fonction.

    Quelle serait la voie la plus élégante?


1 commentaires

Vous devez décider C ou C ++, les approches seront différentes, avec C ++, vous pouvez utiliser RAII mentionné ci-dessous, et avec C, vous devez déplacer la mise en œuvre sur des fonctions distinctes et les originaux deviennent simplement des wrappers qui ont des emballages qui se verrouillent Appelez à la réelle mise en œuvre - Variation de la réponse de @ Frunsi ci-dessous


7 Réponses :


1
votes

Vous pouvez écrire des fonctions d'emballage, par exemple: xxx pré>

avec le pré-processeur, vous pouvez enregistrer du travail. p>

EDIT P> P>

Comme quelqu'un l'a dit, il serait préférable de déplacer les implémentations en fonctions internes de la bibliothèque et de présenter les emballages aux utilisateurs de la bibliothèque, par exemple: p>

void f2_unlocked() {
  ...
  f1_unlocked();
  ...
}

void f2() {
  lock();
  f2_unlocked();
  unlock();
}


7 commentaires

Il a spécifiquement demandé à ne pas utiliser déverrouiller () car il existe de nombreuses déclarations de retour dans ces fonctions; Point n ° 2 dans la question


@Jaywalker, cette solution ne modifie pas la fonction existante, tout simplement wraps il n'y a qu'un seul point de retour et cela fonctionnera également pour C aussi, mais il brise le point 1.


Une fonction d'emballage est nécessaire et RAII est appelée pour empêcher l'appel explicite de la fonction de déverrouillage. Un simple objet sentinelle.


Voir ma réponse pour les détails du préprocesseur ... ce qui devrait bien corriger le point 1


Les fonctions doivent également être convaincables après avoir introduit les serrures. Dans ce cas, tous les appelants devront mettre à jour F1 () à F1_Locked, ce qui n'est pas réalisable.


@sharjeel, mais si vous contrôlez la bibliothèque, pourriez-vous ne pas déplacer le code dans F1 à f1_locked , puis l'original F1 est simplement ce wrapper qui déléguette toujours au F1_Locked - c'est-à-dire inverser les noms de fonction dans cette solution.


@Frunsi ne devrait-il pas être r = f1_unlocked (x); Dans la modification?



3
votes

Définissez l'enveloppe comme ceci:

void func1()
{
    SemaphoreWrapper(global_semaphore);


    ...
}


0 commentaires

16
votes

Utiliser RAII (l'acquisition de ressources est l'initialisation) pour définir le verrou sur le mutex. Cela vous permettrait d'oublier le point n ° 2, c'est-à-dire que vous n'avez pas besoin de suivre la relève de la déclaration de retour pour libérer la serrure.

int f1 (int) {
    Lock l;
    // forget about release of this lock
    // as ~Lock() will take care of it
}


6 commentaires

+1 Raii est la façon C ++ de le faire. La fonction F1 doit également être enveloppée. Ce qui peut être fait avec une fonction de modèle simple (obscurcira un peu le code) ou simplement en créant des wrappers simples comme FRUNSI expliqués.


Une question évidente est que vous pouvez alors protéger trop de fonctions ... et les mutiles ne sont pas réentrantes en général.


S'il vous plaît heed Le conseil de Matthieu M. et utilisez réentrant mutex . Il permet à un seul fil de saisir le mordex n'importe quel nombre de fois, tout en bloquant d'autres threads qui tentent d'appeler la fonction de la bibliothèque. Sinon, il y a un risque de saut de soi d'un seul fil.


@daramarak, pourquoi devons-nous envelopper la fonction F1? Je ne comprends pas très bien. Pouvez-vous clarifier s'il vous plait?


@batbrat pour éviter de changer la fonction F1. Il est préférable de séparer la souci de verrouiller à une fonction d'emballage. En outre, l'OP spécifie que "rien n'a changé ici", ce qui pourrait impliquer que la fonction d'une manière ou d'une autre ne doit pas être touchée.


@darmarak, merci pour la réponse. Cela m'a vraiment aidé à mieux comprendre. :)



0
votes

Les décorateurs sont strictement une caractéristique linguistique qui fournit Syntaxtics Sugar pour la sémantique sous-jacente. La sémantique sous-jacente que vous pouvez obtenir en C ++: il suffit d'envelopper la fonction de manière appropriée; Le sucre syntaxique que vous ne pouvez pas obtenir.

Une meilleure alternative serait de créer une conception de code appropriée qui prend en charge votre cas d'utilisation, par exemple. Par ineritive et le modèle de décorateur . Cela n'implique pas nécessairement l'héritage de classe - le motif peut également être réalisé à l'aide de modèles de classe.

Quant à votre cas d'utilisation spécifique , de meilleures alternatives ont déjà été postées.


0 commentaires

0
votes

Il me semble que ce que vous voulez faire est Programmation orientée verso (AOP ). Il y a peu de cadres AOP en C et C ++, mais d'après ce que j'ai vu il y a quelques mois, je pense que le Projet AspectC ++ Fournit une belle mise en œuvre des concepts AOP. Je ne l'ai pas testé dans le code de production.


0 commentaires

11
votes

Si vous voulez vraiment une solution C, vous pouvez aller avec des macros comme: xxx pré>

mais vous aurez besoin d'une macro pour chaque nombre d'arguments (et la fonction doit avoir un type de retour) . P>

Exemple d'utilisation: P>

LOCKED_FUNCTION_ARG1(int, f1, int, myintarg)
{
   //unchanged code here
}


2 commentaires

Funcname_Locked apparaîtra exactement comme vous l'avez tapé car il n'est pas un paramètre macro. Vous devez concaténer Funcname et _Locké avec l'opérateur de macro ## .


Si vous utilisez C ++, le combine avec boost :: Le préprocesseur serait une solution prometteuse.



1
votes

ne pas.

Vous ne pouvez pas passer d'un seul filetage à la multi-filetage en sensibilisant quelques serrures ici et là et espère le meilleur.

  1. Êtes-vous sûr qu'il n'y a pas deux fonctions qui partagent une variables globales? Les fonctions C sont notoires pour utiliser des tampons alloués statiquement.

  2. Les mutex ne sont normalement pas réentrants. Par conséquent, si vous décorez F1 et F2 et on appelle l'autre, vous allez une impasse. Vous pouvez bien sûr utiliser des mutiles réentrantes plus chères.

    La multithreading est difficile, au mieux de fois. Il faut généralement comprendre et ajuster le flux d'exécution.

    J'ai du mal à imaginer que jeterais quelques serrures qui vont travailler pour le mieux.

    Et cela revient évidemment au fait que si les fonctions n'étaient pas conçues avec MT à l'esprit, elles pourraient bien être plus lentes (avec toutes ces opérations mutex) et vous ne récolterez pas beaucoup d'avantage.

    Si vous avez vraiment besoin de votre sémaphore, faites-le verrer le client. Il devrait savoir quand se verrouiller et quand ne pas.


6 commentaires

Bien que cela dévie de la conversation principale, c'est-à-dire des décorateurs de C, votre point est assez valable pour essayer de créer une seule bibliothèque filetée dans une bibliothèque multi-threadée en mettant quelques serrures.


La bibliothèque est écrite en C qui est appelée par des liaisons Python. Il fonctionne bien dans les threads multiples de Python en raison de la GIL (verrouillage de l'interprète global) qui résout des problèmes de concurrence à un prix très élevé de l'efficacité. J'ai identifié quelques fonctions qui doivent être synchronisées tandis que d'autres sont à peu près inoffensives.


Point mineur, mais errno existe comme une instance séparée pour chaque thread.


Oh et dans la phrase J'ai un peu imaginant que jetant quelques serrures autour fonctionnerait pour le meilleur Vous devez insérer le mot "on" après le quatrième mot :-)


@JeremyP: en ce qui concerne "on", je pense que je vais passer;)


Bien que cela ne soit pas une réponse directe à la question, c'est un très bon conseil. J'aimerais ajouter que même si le code pourrait sembler courir bien après, vous finirez probablement de poursuivre des courses pendant des siècles, car ils ne montrent jamais leurs visages laids tant que le «bon moment» est là. Redesignez pour un filetage multi-threading pour éviter d'avoir cette voix étrange à l'arrière de votre tête tout en débogage; "Ce pourrait être une autre race?"