OVH Cloud OVH Cloud

Design d'une queue d'ordres

6 réponses
Avatar
Fabien LE LEZ
Bonjour,

Encore une fois se pose à moi un problème que je crois courant, mais
auquel je ne trouve pas de solution élégante.

J'ai un programme multithread :
- un thread s'occupe de traiter des données, de façon asynchrone ;
- l'autre s'occupe de l'interface utilisateur.

J'ai une classe "GestionDonnees", qui contient une queue d'ordres. Le
thread "interface utilisateur" ajoute des ordres à cette queue, et le
thread "traitement de données" recupère les ordres un par un, et les
traite.
Ainsi, seule la queue doit être protégée par un mutex.

Comme les ordres ont des données différentes, j'ai créé une hiérarchie
de classes :

class GestionDonnees
{
public:
class Ordre
{
public:
virtual void DoIt (GestionDonnees&) const= 0;
};
class Ordre_Ouvrir: public Ordre
{
public:
Ordre_Ouvrir (std::string const& nom_fichier);
virtual void DoIt (GestionDonnees&) const;
private:
std::string const nom_fichier;
};
class Ordre_Atteindre: public Ordre
{
public:
Ordre_Atteindre (size_t offset);
virtual void DoIt (GestionDonnees&) const;
private:
size_t const offset;
};
class Ordre_RemplaceChaines: public Ordre
{
public:
Ordre_RemplaceChaines (std::string const& a_remplacer,
std::string const& remplacement);
virtual void DoIt (GestionDonnees&) const;
private:
std::string const& a_remplacer;
std::string const& remplacement;
};

void AjouterOrdre (Ordre const*);

private:
std::queue<Ordre const*> ordres;

Fichier fichier_en_cours;
};

Jusque-là, tout va bien : le thread de gestion de données récupère le
premier ordre dans la queue, et appelle la fonction virtuelle
"DoIt()".
Le problème est dans l'implémentation de cette fonction, qui doit
accéder aux données privées de GestionDonnees.
Par exemple, Ordre_Ouvrir::DoIt() doit pouvoir accéder à
GestionDonnees::fichier_en_cours.

Je ne peux mettre aucun accesseur public (même pas un
GestionDonnees::Ouvrir()), car il ne doit pas être possible de
modifier un GestionDonnees autrement que par ce système d'ordres.

Je n'aime pas non plus l'idée de déclarer toutes les classes Ordre_*
amies de GestionDonnees.

Pour l'instant, j'en suis réduit à une bidouille : j'utilise l'idiome
pimpl, mais avec un pointeur public.

class GestionDonnees
{
public:
class Impl;
Impl* GetImpl() { return pimpl; }
private:
Impl* pimpl;
...

Ainsi, les Ordre_* peuvent appeler les fonctions de
GestionDonnees::Impl, mais le code client ne le peut pas, car il ne
voit pas la définition de cette classe Impl.

Si quelqu'un a une meilleure idée...

Merci d'avance.

6 réponses

Avatar
Fabien LE LEZ
Il y a une vingtaine de minutes, j'ai peut-être bien raconté des
conneries.

Si je nettoie un peu tout le bazar, en époussetant les deux neurones
qui me restent (pendant les brefs instants où ils ne se battent pas en
duel), j'arrive à :

class GestionDonnees
{
public:
class Impl;

class Ordre
{
public:
virtual void DoIt (GestionDonnees::Impl&) const= 0;

};
private;

Impl* pimpl;

On retrouve l'idiome pimpl presque classique : la classe Impl est
publique, tout en ne l'étant pas vraiment puisque son implémentation
n'est pas visible par le code client.

J'avoue que je ne sais pas trop si cette situation est satisfaisante.
Est-ce "propre" ? Est-ce une sale bidouille ? Est-ce une bidouille
qu'on ne peut pas éviter à cause des limitations du C++ ?


Je m'écarte du sujet, mais maintenant que j'y pense, je verrais bien
un truc du style :

void f();

class C
{
nearly-private:
void m();
nearly-friend void f();

private:
void m2();
};

qui voudrait dire "f() peut accéder à m() mais pas à m2()".

Le fait est qu'on peut faire ce genre de choses en C++, mais avec des
circonvolutions qui n'améliorent pas la lisibilité du code :-/

Avatar
Arnaud Debaene
"Fabien LE LEZ" a écrit dans le message de news:

Bonjour,

<snip>

class Ordre_RemplaceChaines: public Ordre
{
public:
Ordre_RemplaceChaines (std::string const& a_remplacer,
std::string const& remplacement);
virtual void DoIt (GestionDonnees&) const;
private:
std::string const& a_remplacer;
std::string const& remplacement;
};
<snip>


Petite remarque sans rapport avec ta question : c'est sans doute une
mauvaise idée de conserver des références dans un objet Ordre, si celui-ci
est destiné à être transmis d'un thread à l'auter via une file de messages.
Tout dépend bien sûr de l'application, mais de manière générale, rien ne
garantit que les objets référéncés par "a_remplacer" et "remplacement"
soient encore en vie au moment de l'appel à DoIt. C'est probablement plus
prudent de faire une copie de ces chaines.

Arnaud

Avatar
Jean-Marc Bourguet
Ton probleme ressemble a celui que j'avais pose il y a quelque temps.
Je crains qu'une bonne solution necessite un controle d'acces
different de celui du C++. La technique que j'utilise se sert de
notre organisation du code et utilise le fait que le code qui n'a pas
le droit d'utiliser des definitions n'est pas compile avec un chemin
de recherche capable de les trouver.

Une technique possible pour arriver a ton quasi-friend, est d'utiliser
une classe intermediaire qui a en prive l'interface que tu veux
restreindre et qui a un friend tous les utilisateurs possibles. Mais
je trouve ceci moins souple car ajouter un utilisateur entraine la
modification de cette classe et donc la recompilation de tous les
autres.

Si tu trouves une bonne solution, ca m'interesse toujours.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
Fabien LE LEZ
On 08 Jun 2006 09:12:44 +0200, Jean-Marc Bourguet :

Une technique possible pour arriver a ton quasi-friend, est d'utiliser
une classe intermediaire


Oui.
C'est ce que je disais : il existe des techniques pour le faire, mais
en "bricolant" et en obfuscant...

qui a en prive l'interface que tu veux
restreindre et qui a un friend tous les utilisateurs possibles. Mais
je trouve ceci moins souple car ajouter un utilisateur entraine la
modification de cette classe et donc la recompilation de tous les
autres.


Et surtout, ajouter un utilisateur devient plus compliqué, puisqu'il
faut le rajouter dans la liste.

Avatar
Fabien LE LEZ
On Thu, 8 Jun 2006 07:56:55 +0200, "Arnaud Debaene"
:

std::string const& a_remplacer;
std::string const& remplacement;
};
<snip>


Petite remarque sans rapport avec ta question : c'est sans doute une
mauvaise idée de conserver des références dans un objet Ordre,


Oui. Faute de frappe. Il fallait lire :

std::string const a_remplacer;
std::string const remplacement;


Avatar
kanze
Fabien LE LEZ wrote:

Il y a une vingtaine de minutes, j'ai peut-être bien raconté des
conneries.

Si je nettoie un peu tout le bazar, en époussetant les deux
neurones qui me restent (pendant les brefs instants où ils ne
se battent pas en duel), j'arrive à :

class GestionDonnees
{
public:
class Impl;

class Ordre
{
public:
virtual void DoIt (GestionDonnees::Impl&) const= 0;

};
private;

Impl* pimpl;

On retrouve l'idiome pimpl presque classique : la classe Impl
est publique, tout en ne l'étant pas vraiment puisque son
implémentation n'est pas visible par le code client.


Et aussi, le grand public n'a pas accès à ton objet
d'implémentation. Fait que le constructeur de Impl soit privé,
et que GestionDonnees soit ami, et même s'il arrive à avoir
accès à la declaration, il n'y peu rien, parce qu'il n'a pas de
moyen d'en obtenir une instance. Pour en obtenir une instance,
il faut soit être un GestionDonnees (pour pouvoir en appeler le
constructeur), soit le recevoir d'une instance de GestionDonnees
(qui ne va pas le donner à n'importe qui).

Sans passer par un pimpl, tu peux aussi rendre uniquement la
classe de base amie, et y ajouter les fonctions d'accès pour
tous les membres de GestionDonnees. Ou seulement aux membres qui
doit être accèssible aux ordres : si j'ai bien compris, les
ordres ne doivent pas aller tripoter dans la queue, par exemple.

(Aussi, si d'autres classes doivent y accéder, je ne
l'appellerais pas pimpl, mais plutôt quelque chose du genre
SharedData. Dans ton exemple ci-dessus, par exemple, la queue
pourrait bien encore être membre direct de GestionDonnees, pour
que les ordres n'y ait pas d'accès, même en recevant un pointeur
aux données partagées. Et le nom avertit bien le lecteur qu'il
ne s'agit pas de l'idiome du pimpl, mais bien des données
partagées entre GestionDonnees et les ordres.)

J'avoue que je ne sais pas trop si cette situation est
satisfaisante. Est-ce "propre" ? Est-ce une sale bidouille ?
Est-ce une bidouille qu'on ne peut pas éviter à cause des
limitations du C++ ?


Je n'y vois rien de malpropre (sauf peut-être l'utilisation du
nom pimpl quand ce n'en est pas un). Je ne crois pas non plus
qu'elle soit liée aux limitations du C++. Au contraire, je
trouve qu'elle exploite la souplesse du langage pour donner un
niveau de contrôle d'accès qui n'est souvent même pas possible
dans d'autres langages.

Je m'écarte du sujet, mais maintenant que j'y pense, je
verrais bien un truc du style :

void f();

class C
{
nearly-private:
void m();
nearly-friend void f();

private:
void m2();
};

qui voudrait dire "f() peut accéder à m() mais pas à m2()".


Et puis, il y aurait f2(), qui peut accéder aux deux, et... Il
faudrait en fin de compte spécifier pour chaque fonction
l'ensemble d'autres fonctions ou classes qui pourraient y
accéder.

Ça donnerait bien un contrôle accru. Mais est-ce que les
benefices sont assez pour justifier l'augmentation de la
complexité ? Personalement, je le doute.

Le fait est qu'on peut faire ce genre de choses en C++, mais
avec des circonvolutions qui n'améliorent pas la lisibilité du
code :-/


Attention : ce qui pose le problème de lisibilité ici, c'est le
fait que tu as appelé pimpl quelque chose qui n'en est pas un.
Avoir un ensemble de données dont la gestion et la manipulation
est partagées entre plusieurs classes n'est pas un problème en
soi, pourvue que les noms et la documentation rendent clair que
c'est le cas.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34