Hello,
Je bosse sur une petite bibliothèque type CEGUI, et j'ai besoin d'utiliser des Vector2, Point, Rectangle, ....
Quelle est la meilleure façon de faire ?
J'en vois 4, regroupées en 2 approches :
1a : Fournir une implémentation perso de ces classes, l'utilisateur se démerde pour convertir ses vecteurs en mes vecteurs quand il interagi avec la lib.
Beaucoup de libs font ça. L'exemple le plus marquant est probablement Qt : on doit convertir ses std::vector en QVector et vice-versa.
1b : La même chose mais en utilisant une implémentation de Vector2, Point, Rectangle, ... existante : on évite de recoder l'existant, mais le problème de conversion est toujours là.
2a : On passe les types de vecteurs en paramètre template à la lib et on utilise le pattern adapter. Avec une classe statique.Avantages :
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 template <class T> struct Vector2 { Vector2 operator+(Vector2 const& rhs) const { return *this; } Vector2 operator-(Vector2 const& rhs) const { return *this; } }; template <class T> struct DefaultTypes { typedef Vector2<T> Vec2; typedef Vector2<T> Point; }; template <class T> struct DefaultAdapter { static T add(T const& lhs, T const& rhs) { return lhs + rhs; } static T sub(T const& lhs, T const& rhs) { return lhs - rhs; } // ... }; template <class T=DefaultTypes<float>, template<class> class Adapter=DefaultAdapter> struct Foo { typedef typename T::Vec2 Vec2; typedef typename T::Point Point; virtual ~Foo() {} void bar() { Vec2 a, b, c, d; // a = b + c a = Adapter<Vec2>::add(b, c); //a = b + c + d a = Adapter<Vec2>::add(Adapter<Vec2>::add(b, c), d); } }; template <class T=DefaultTypes<float>, template<class> class Adapter=DefaultAdapter> struct Bar: Foo<T, Adapter> { typedef typename T::Vec2 Vec2; typedef typename T::Point Point; void foobar() { Vec2 a, b, c, d; // a = b + c a = Adapter<Vec2>::add(b, c); //a = b + c + d a = Adapter<Vec2>::add(Adapter<Vec2>::add(b, c), d); } }; int main() { Foo<> f; f.bar(); Bar<> b; b.foobar(); return 0; }
Pas de problèmes de conversion.
Pas de surcout à l'exécution. (je pense ?)
Inconvénients :
Je perd l'utilisation des opérateurs dans le code de la lib (Vector2::operator+ devient Adapter<Vector2>::add).
a = b + c + d est dur à écrire : a = Adapter<Vec2>::add(Adapter<Vec2>::add(b, c), d);.
2b : La même chose mais en wrappant les vecteurs dans des objets dont je connais l'interface.Avantages :
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 template <class T> struct Vector2 { Vector2 operator+(Vector2 const& rhs) const { return *this; } Vector2 operator-(Vector2 const& rhs) const { return *this; } }; template <class T> struct DefaultTypes { typedef Vector2<T> Vec2; typedef Vector2<T> Point; }; template <class T> struct DefaultWrapper { typedef T wrapped_type; wrapped_type& ref; DefaultWrapper(wrapped_type& ref): ref(ref) { } wrapped_type operator+(DefaultWrapper const& rhs) const { return ref + rhs.ref; } // ... }; template <template<class> class Wrapper> struct allow_w { template <class T> Wrapper<T> w(T& ref) { return Wrapper<T>(ref); } }; template <class T=DefaultTypes<float>, template<class> class Wrapper=DefaultWrapper> struct Foo: protected allow_w<Wrapper> { typedef typename T::Vec2 Vec2; typedef typename T::Point Point; virtual ~Foo() {} void bar() { Vec2 a, b, c, d; // a = b + c a = w(b) + w(c); //a = b + c + d a = w(w(b) + w(c)) + w(d); } }; template <class T=DefaultTypes<float>, template<class> class Wrapper=DefaultWrapper> struct Bar: Foo<T, Wrapper> { typedef typename T::Vec2 Vec2; typedef typename T::Point Point; void foobar() { Vec2 a, b, c, d; // a = b + c a = w(b) + w(c); //a = b + c + d a = w(w(b) + w(c)) + w(d); } }; int main() { Foo<> f; f.bar(); Bar<> b; b.foobar(); return 0; }
Pas de problèmes de conversion.
Accès aux opérateurs
Inconvénients :
Surcout éventuel à l’exécution (à moins que la création des wrappers ne soit supprimée par optimisation ?)
a = b + c + d reste dur à écrire, le fait de devoir wrap le résultat intermédiaire n'est pas pratique.
Dans les cas 2a et 2b, le code doit être fourni, une version pré-compilé ne peut pas être fournie à l'utilisateur mais ça ne pose pas de problème ici. (Peut être possible via type-erasure, au prix d'un surcout supplémentaire ?).
La version 2a me semble être le meilleur compromis, vous en pensez quoi ? Une meilleure solution existe ?
(Au passage, 2b semble être un exemple d'un des rares cas d'utilisation de l'héritage protégé *_*).
J'en profite pour poser une autre question au passage : pourquoi je dois écrire a = Adapter<Vec2>::add(b, c); ?
La déduction de type ne peut pas être faite car il faut "fixer" une classe avant d'aller chercher la bonne fonction ?
Est-il préférable d'avoirAu lieu de
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 struct DefaultAdapter { template <class T> static T add(T const& lhs, T const& rhs) { return lhs + rhs; } template <class T> static T sub(T const& lhs, T const& rhs) { return lhs - rhs; } // ... };Si oui, pourquoi ?
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 template <class T> struct DefaultAdapter { static T add(T const& lhs, T const& rhs) { return lhs + rhs; } static T sub(T const& lhs, T const& rhs) { return lhs - rhs; } // ... };
Partager