2010-08-08 17 views
2

Mon conteneur doit stocker un peu d'informations sur ses éléments. Normalement, je stocke cela séparément des éléments. Cependant, je voudrais donner aux utilisateurs la possibilité de conserver la mémoire en dédiant un champ dans le type de structure d'élément pour un usage externe. E.g .:Dédicace d'un champ dans des types de classe arbitraires pour «utilisation externe»

struct MyStuff 
{ 
    int   foo; 
    char   bar; 
    mutable char dedicated_for_external_use; // Because of alignment, this field 
              // won't increase sizeof (MyStuff) 
}; 

L'idée ici est que le champ ne doit pas être accessible par autre chose que le conteneur de l'élément. Puisque les conteneurs stockent une copie (un peu comme std::vector), il ne serait pas un problème si vous avez ajouté une valeur donnée x à plusieurs conteneurs.

Comment concevez-vous une interface pour cela qui, si possible, répondrait aux exigences suivantes?

  • Devrait être complètement facultatif. C'est à dire. il devrait être possible de automatiquement déterminer si un type donné fournit un tel champ ou non, puis le conteneur ne l'utiliserait que s'il était disponible.
  • Idéalement, ne dépendrait pas des traits de caractères etc. car j'ai besoin de la compatibilité maximale du compilateur.
  • Devrait être facile à utiliser. C'est à dire. Si vous le pouvez et que vous voulez activer cette optimisation pour le type MyStuff, vous pouvez le faire avec 3 lignes de code, pas 25. Les complications internes, d'un autre côté, n'a pas d'importance.
  • Doit de préférence exclure complètement les faux positifs. Ce que je veux dire est: si vous vérifiez pour le champ foo_bar il y a une petite possibilité qu'un tel champ existe pour une raison complètement indépendante (et je pense que le dactylographie n'est simplement pas pour C++). Une meilleure façon serait de vérifier si le type hérite de la classe de marqueur ProvidesExternalUseField de ma bibliothèque, car cela ne peut pas être accidentel.

EDIT

Je sais Boost.Intrusive, mais ce que je veux est quelque chose de différent. Si je vais comme ça et que je crée une classe de hooks avec un seul champ char, il ne peut pas être utilisé pour conserver la mémoire dans de nombreux cas. Si le type hérité a un int comme premier champ, le champ char sera complété à 4 octets. C'est à dire. vous auriez souvent besoin de connaissances complexes de types internes pour être en mesure de « presser » tel champ extern utilisation, mais l'héritage ne fournit pas vraiment:

struct hooks { mutable char dedicated_for_external_use; }; 
struct MyStuff : hooks 
{ 
    int   foo; 
    char   bar; 
}; 

Ici, la taille de MyStuff sera de 12 octets, pas 8.

+0

Peut-être Boost.Intrusive (http://www.boost.org/doc/libs/1_43_0/doc/html/intrusive.html) et 'boost :: intrusive_ptr' (http://www.boost.org/ doc/libs/1_43_0/libs/smart_ptr/intrusive_ptr.html) pourrait donner quelques inspirations. – Philipp

+1

Quand vous dites "compatibilité maximale du compilateur", qu'est-ce que cela signifie en pratique? À quel point les compilateurs dont vous avez besoin sont-ils incompatibles? – jalf

+0

@jalf: Eh bien, de préférence, il devrait fonctionner sur un compilateur compatible C++ 98. Je voulais juste dire que je préférerais faire sans extensions non standard ou C++ 0x si possible. – doublep

Répondre

0

Vous pouvez utiliser la spécialisation de modèle partielle pour le cas où votre structure de données dérive de l'interface de marqueur.

Disons que votre classe d'interface marqueur ressemble à ceci:

class ProvidesExternalUseField 
{ 
public: 
    char GetExtraField() { return 0; } 
    void SetExtraField (char newVal) {} 
}; 

Il n'est pas virtuel dans un but: nous ne voulons pas ajouter un pointeur vtable à une classe de données juste pour cela.

Maintenant, nous allons mettre en œuvre une classe simple conteneur:

template <class T> 
class Container 
{ 
public: 
    char GetExtraValue() 
    { 
     return 0; // here we cannot know if T is derived from the marker 
    } 
private: 
    T m_t; 
}; 

Et voici comment nous changeons de faire la distinction entre les 2 cas:

template <class T, bool DoesTProvideExternalUseField> 
class ContainerImpl 
{ 
public: 
    char GetExtraValue() { return 0; } 

private: 
    T m_t; 
}; 

template <class T> 
class ContainerImpl<T, true> 
{ 
public: 
    char GetExtraValue() { return m_t.GetExtraField(); } 
private: 
    T m_t; 
}; 

template <class T> 
class Container: public ContainerImpl<T, 
             boost::is_base_of<ProvidesExternalUseField,T>::value> 
{ 
}; 

vous pouvez maintenant définir les struct comme celui-ci:

struct A 
{ 
    int m_intVal; 
}; 

struct B: public ProvidesExternalUseField 
{ 
    char GetExtraField() { return m_extraField; } 
    void SetExtraField (char newVal) { m_extraField = newVal; } 

    int m_intVal; 
    char m_charVal; 
    char m_extraField; 
}; 

Et d'utiliser la classe de conteneur de la même manière:Vous pouvez également automatiser (modéliser) les getters et les setters dans l'interface de marqueur en utilisant un paramètre poiter-to-member en tant que paramètre de modèle.