Bonjour,
Dans le cadre d'un développement embarqué, j'ai besoin de contrôler l'accès à un bus SPI. L'objectif est d'empêcher les utilisations concurrentes du bus. Pour cela, on doit respecter l'enchainement :
- lock
- utilisation
- unlock
En fan de Scott Meyers, j'applique l'adage "les API doivent être simple à utiliser correctement et difficile à utiliser incorrectement". Il faut :
- ne pas pouvoir oublier de faire unlock() --> RAII
- ne pas pouvoir travailler sans avoir fait lock() --> renvoyer un objet permettant l'utilisation quand on fait lock()
J'aimerais vous montrer mon code pour que vous me donniez vos avis. Les commentaires décrivent les buts de chaque classe.
Le sortie console montre que ça se passe bien :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108 #include <iostream> #include <mutex> class Mutex { /* * arm-gcc-none-eabi ne fournit pas std::mutex. * j'ai donc recodé une classe avec la même API */ public: void lock() { std::cout << __PRETTY_FUNCTION__ << std::endl; mutex_m.lock(); } void unlock() { std::cout << __PRETTY_FUNCTION__ << std::endl; mutex_m.unlock(); } private: std::mutex mutex_m; // pour remplacer les mutex natives lors d'un test sur PC }; /* * La ressource materielle. * Le code applicatif n'utilise pas directement cette classe. */ class Spi { public: bool send(std::uint8_t byte) const { std::cout << __PRETTY_FUNCTION__ << " " << (int) byte << std::endl; return true; } }; /* * Le contrôleur des accès concurrents sur le bus. */ class SpiBus { public: /* * Objet renvoyé par SpiBus::lock() */ class LockedSpiBus : public Spi { public: /* * RAII: on verrouille mutex à la création */ LockedSpiBus(Mutex& mutex) : mutex_m(mutex) { std::cout << __PRETTY_FUNCTION__ << std::endl; mutex_m.lock(); } /* * RAII: on déverouille à la destruction */ ~LockedSpiBus() { std::cout << __PRETTY_FUNCTION__ << std::endl; mutex_m.unlock(); } /* * Il y plus de fonctionnalités dans LockedSpiBus que dans Spi, * comme la sélection d'adresse sur le bus. */ void workWithBus() { std::cout << __PRETTY_FUNCTION__ << std::endl; } private: Mutex& mutex_m; }; /* * Renvoyer un SpiLockBus permet d'éviter de travailler sur le bus avant de l'avoir verrouiller. */ LockedSpiBus lock() { std::cout << __PRETTY_FUNCTION__ << std::endl; auto locked = LockedSpiBus(mutex_m); return locked; } /* * Peut-être supprimée ? */ void unlock() { mutex_m.unlock(); std::cout << __PRETTY_FUNCTION__ << std::endl; } private: Mutex mutex_m; }; /* * Exemple d'utilisation. */ int main() { SpiBus bus; auto locked_bus = bus.lock(); locked_bus.workWithBus(); locked_bus.send(42); //bus.unlock(); // optionnel }
Pour ne pas alourdir le code, je n'ai pas supprimé tous les constructeurs et opérateurs de copie mais je vais bien sûr le faire dans la version réelle. Quid des constructeurs et opérateurs de déplacement par contre ?SpiBus::LockedSpiBus SpiBus::lock() SpiBus::LockedSpiBus::LockedSpiBus(Mutex&) void Mutex::lock() void SpiBus::LockedSpiBus::workWithBus() bool Spi::send(uint8_t) const 42 SpiBus::LockedSpiBus::~LockedSpiBus() void Mutex::unlock()
Merci pour vos retours![]()
Partager