Dans un autre fil, j'ai commencé un hors sujet sur la comparaison virtuelle.
Admettons que l'on ait envie de stocker, dans une même table de hachage, des objets de type hétérogène, mais qui dérivent tous de la même classe de base Base. Ces objets ont une sémantique de valeur.
Dans ce cas, je pense que l'on peut faire un code dans ce genre-là :
J'ai implémenté bool Base::equals(const Base& other) const de telle sorte que l'on puisse facilement créer de nouvelles classes qui dérivent de Base ou de Deriv, tout en respectant la contrainte selon laquelle equals reste une relation d'équivalence, c'est à dire :
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 class Base { friend bool operator==(const Base& lhs, const Base& rhs); public: bool equals(const Base& other) const { return canEqual_1(other) && other.canEqual_2(*this); } size_t hash() const { return virtHash(); } // ... private: virtual bool canEqual_1(const Base& other) const { return (*this == other); } virtual bool canEqual_2(const Base& other) const { return true; } virtual size_t virtHash() const { // ... } // ... }; class Deriv : public Base { friend bool operator==(const Deriv& lhs, const Deriv& rhs); private: // Ici, par exemple, on impose que le type dynamique de other soit Deriv ou dérive de Deriv // pour que this->equals(other) puisse être vrai. bool canEqual_1(const Base& other) const override { const Deriv* that = dynamic_cast<const Deriv*>(&other); return that != nullptr && (*this == *that); } bool canEqual_2(const Base& other) const override { const Deriv* that = dynamic_cast<const Deriv*>(&other); return that != nullptr; } size_t virtHash() const override { // ... } // ... }; class BaseWrapper { friend bool operator==(const BaseWrapper& lhs, const BaseWrapper& rhs) { return lhs.m_base->equals(*rhs.m_base); } private: std::unique_ptr<Base> m_base; public: explicit BaseWrapper(std::unique_ptr<Base> base) : m_base(std::move(base)) {} size_t hash() const { return m_base->hash(); } }; namespace std { template<> struct hash<BaseWrapper> { typedef BaseWrapper argument_type; typedef std::size_t result_type; result_type operator()(argument_type const& bw) const { return bw.hash(); } }; } int main() { std::unordered_set<BaseWrapper> mySet; mySet.insert(BaseWrapper(std::make_unique<Base> (/* paramètres */))); mySet.insert(BaseWrapper(std::make_unique<Deriv>(/* paramètres */))); return 0; }
- réflexive : pour tout x, x.equals(x) ;
- symétrique : pour tout couple (x, y), x.equals(y) == y.equals(x) ;
- transitive : pour tout triplet (x, y, z), si x.equals(y) && y.equals(z) alors x.equals(z).
(Les objets x, y et z peuvent être de types dynamiques différents.)
Mon implémentation de equals utilise indirectement des dynamic_cast, mais je pense que c'est justifié.
Qu'en pensez-vous ?
Partager