I. L'article original▲
Cet article est une adaptation en langue française de A Weak Hash Table implementation with Qt.
II. Le principe▲
L'implémentation proposée utilise les pointeurs intelligents de Qt suivants :
- QSharedPointer : cette classe contient une référence forte sur un pointeur partagé. C'est automatique, ce qui signifie qu'elle va supprimer le pointeur contenu quand il sort de portée, pourvu qu'aucun autre QSharedPointer ne le référence ;
- QWeakPointer : ette classe contient une référence faible sur un pointeur.
Comme le lecteur attentif l'aura remarqué, on utilisera des références faibles comme valeurs dans la table de hachage et des références fortes dans le reste du programme.
La prochaine chose à faire est de détecter dans la table le moment où les valeurs deviennent nulles (c'est-à-dire quand l'objet pointé est détruit parce qu'il n'a plus aucune référence). L'implémentation proposée requiert que les valeurs stockées dans la table héritent de QObject ; c'est très pratique car, ainsi, ils émettront un signal destroyed(QObject*) juste avant la destruction proprement dite. On peut alors facilement connecter ce signal à un slot dans la table pour enlever la paire correspondante.
Un dernier problème : quand on reçoit ce signal, trouver la paire correspondante dans la table est normalement très inefficace. En réalité, une table de hachage est optimisée pour une recherche rapide par la clé, non par la valeur. Pour résoudre ce problème, on utilise le système de propriétés dynamiques de QObject : la table de hachage définira une nouvelle propriété dynamique pour les QObject qui y sont insérés. Cette propriété indique la clé correspondante dans la table. Une fois ceci fait, la table doit simplement lire cette propriété quand un QObject est détruit pour le trouver en temps constant (au lieu d'un temps linéaire). Ceci apporte cependant une limitation : la clé doit être stockable dans un QVariant, le type de données des propriétés dynamiques. L'implémentation fournie dans cet article utilise le type QString pour les clés, mais ceci peut être facilement adapté.
III. L'implémentation▲
Voici le code de cette table de hachage à référence faible.
#include
<QHash>
#include
<QString>
#include
<QStringList>
#include
<QWeakPointer>
#include
<QSharedPointer>
#include
<QObject>
/*!
* Implémentation d'une table de hachage à référence faible avec Qt.
* Chaque paire clé-valeur est automatiquement retirée de la table
* quand la valeur n'est plus utilisée dans le reste du programme.
*/
class
WeakHashTable: public
QObject
{
Q_OBJECT
public
:
WeakHashTable(QObject *
parent =
0
);
void
insert(const
QString &
key, const
QSharedPointer<
QObject>
&
value);
QSharedPointer<
QObject>
take(const
QString &
key);
bool
remove(const
QString &
key);
const
QSharedPointer<
QObject>
value(const
QString &
key) const
;
const
QSharedPointer<
QObject>
operator
[](const
QString &
key) const
;
inline
int
size() const
{
return
m_table.size(); }
inline
bool
contains(const
QString &
key) const
{
return
m_table.contains(key);
}
inline
bool
isEmpty() const
{
return
m_table.isEmpty(); }
inline
QStringList keys() const
{
return
m_table.keys(); }
private
slots:
void
removeValue(QObject*
value);
private
:
void
disconnectSignals(const
QSharedPointer<
QObject>
&
ptr) const
;
private
:
QHash<
QString, QWeakPointer<
QObject>
>
m_table;
}
;
#include
<QVariant>
#include
<QDebug>
#include
"weakhashtable.h"
const
char
*
PROPERTY_KEY =
"WeakHashTableKey"
;
WeakHashTable::
WeakHashTable(QObject *
parent): QObject(parent)
{
}
void
WeakHashTable::
insert(const
QString &
key, const
QSharedPointer<
QObject>&
value)
{
Q_ASSERT(!
value.isNull());
// On s'assure que la valeur est retirée de la table
// à la destruction.
value->
setProperty(PROPERTY_KEY, key);
connect(value.data(), SIGNAL(destroyed(QObject*
)),
SLOT(removeValue(QObject*
)));
// Insertion proprement dite.
m_table.insert(key, value.toWeakRef());
}
const
QSharedPointer<
QObject>
WeakHashTable::
value(const
QString &
key) const
{
return
m_table.value(key).toStrongRef();
}
void
WeakHashTable::
removeValue(QObject *
value)
{
const
QString key =
value->
property(PROPERTY_KEY).toString();
qDebug() <<
"Removing value with key"
<<
key;
Q_ASSERT(!
key.isEmpty());
Q_ASSERT(m_table.contains(key));
if
(!
key.isNull())
m_table.remove(key);
}
bool
WeakHashTable::
remove(const
QString &
key)
{
QSharedPointer<
QObject>
ptr =
m_table.take(key).toStrongRef();
if
(ptr.isNull())
return
false
;
disconnectSignals(ptr);
return
true
;
}
QSharedPointer<
QObject>
WeakHashTable::
take(const
QString &
key)
{
QSharedPointer<
QObject>
ptr =
m_table.take(key).toStrongRef();
if
(!
ptr.isNull())
disconnectSignals(ptr);
return
ptr;
}
const
QSharedPointer<
QObject>
WeakHashTable::
operator
[](const
QString &
key) const
{
return
m_table[key].toStrongRef();
}
void
WeakHashTable::
disconnectSignals(const
QSharedPointer<
QObject>
&
ptr) const
{
Q_ASSERT(!
ptr.isNull());
disconnect(ptr.data(), SIGNAL(destroyed(QObject*
)),
this
, SLOT(removeValue(QObject*
)), Qt::
DirectConnection);
}
IV. Un exemple d'utilisation▲
On l'utilise comme ceci :
WeakHashTable h;
// La classe Contact hérite de QObject.
QSharedPointer<
Contact>
friend1 =
QSharedPointer<
Contact>
(new
Contact("Bob"
));
h.insert("Bob"
, friend1);
// On la récupère alors.
QSharedPointer<
Contact>
myfriend =
h.value("Bob"
);
V. Remerciements▲
Merci à Guillaume Belz et Stoyak pour leur relecture attentive !