Question

J'ai écrit une classe variante, qui sera utilisée comme type principal dans une langue dynamique, qui permettra finalement 256 types de valeur différents (l'en-tête est un octet non signé, seulement 20 sont réellement utilisés). Je veux maintenant implémenter le casting / la conversion entre les types.

Ma pensée initiale était une table de recherche, mais la quantité de mémoire de cisaillement qui aurait besoin le rend peu pratique à mettre en œuvre.

Quelles sont les alternatives? En ce moment, j'envisage trois autres méthodes de la recherche et des suggestions d'autres personnes:

  1. Regrouper les types en sous-ensembles plus grands, tels que numérique ou collection ou autre.
  2. Faire une interface de conversion qui a des méthodes Cancast (de, à) et Cast (variant) et permettez aux classes qui implémentent cette interface à ajouter à une liste, qui peut ensuite être vérifiée pour voir si l'une des classes de conversion peut faire le casting.
  3. Semblable à (1) mais faites plusieurs types de maîtres, et la coulée est un processus en deux étapes du type d'origine au type maître, puis à nouveau au type final.

Quel serait le meilleur système?

Edit: J'ai ajouté la prime car je ne suis toujours pas sûr du meilleur système, la réponse actuelle est très bonne, et j'ai certainement eu mon +1, mais il doit y avoir des gens qui ont fait cela et peuvent dire quelle est la meilleure méthode.

Était-ce utile?

La solution

Mon système est très "lourd" (beaucoup de code), mais très rapide et très riche en fonctionnalités (C ++ multiplateforme). Je ne sais pas jusqu'où vous voudriez aller avec votre conception, mais voici les plus grandes parties de ce que j'ai fait:

DatumState - Classe tenant une "énumération" pour "type", plus la valeur native, qui est une "union" parmi tous les types primitifs, y compris void*. Cette classe est découplée de tous types et peut être utilisée pour tous les types natifs / primitifs, et "référence" à " void* taper. Depuis le "enum" a aussi "VALUE_OF" et "REF_TO"Contexte, cette classe peut se présenter comme" contenant entièrement " float (ou un type primitif), ou "référençant mais pas copieux" float (ou un type primitif). (J'ai vraiment "VALUE_OF", "REF_TO", et "PTR_TO"Contextes pour que je puisse logiquement stocker un évaluer, un référence que cannot-be null, ou un pointeur-that-mai-null-ou pas, et que je sais que je dois supprimer.)

Datum - Classe contenant entièrement un DatumState, mais qui étend son interface pour s'adapter à divers types "bien connus" (comme MyDate, MyColor, MyFileName, etc.) Ces types bien connus sont en fait stockés dans le void* à l'intérieur de DatumState membre. Cependant, parce que le "enum"Partie du DatumState a la "VALUE_OF" et "REF_TO"contexte, il peut représenter un"pointer-to-MyDate" ou "value-of-MyDate".

DatumStateHandle - une classe de modèle d'assistance paramétré avec un type (bien connu) (comme MyDate, MyColor, MyFileName, etc.) Il s'agit de l'accessoire utilisé par Datum pour extraire l'état du type bien connu. L'implémentation par défaut fonctionne pour la plupart des classes, mais toute classe avec une sémantique spécifique pour l'accès remplace simplement son modèle de paramétrage / implémentation spécifique pour une ou plusieurs fonctions membres dans cette classe de modèle.

Macros, helper functions, and some other supporting stuff - pour simplifier "l'ajout" de types bien connus à mon Datum/Variant, J'ai trouvé pratique de centraliser la logique en quelques macros, de fournir des fonctions de support comme la surcharge de l'opérateur et d'établir d'autres conventions dans mon code.

En tant qu'effet secondaire de cette implémentation, j'ai obtenu des tonnes d'avantages, y compris la sémantique de référence et de valeur, des options pour «null» sur tous les types et la prise en charge des conteneurs hétérogènes pour tous les types.

Par exemple, vous pouvez créer un ensemble d'entiers et les indexer:

int my_ints[10];
Datum d(my_ints, 10/*count*/);
for(long i = 0; i < d.count(); ++i)
{
  d[i] = i;
}

De même, certains types de données sont indexés par les chaînes ou par des énumérations:

MyDate my_date = MyDate::GetDateToday();
Datum d(my_date);
cout << d["DAY_OF_WEEK"] << endl;
cout << d[MyDate::DAY_OF_WEEK] << endl; // alternative

Je peux stocker des ensembles d'articles (nativement), ou des ensembles de-Datums (envelopper chaque élément). Pour les deux cas, je peux "déballer" récursivement:

MyDate my_dates[10];
Datum d(my_dates, 10/*count*/);
for(long i = 0; i < d.count(); ++i)
{
  cout << d[i][MyDate::DAY_OF_WEEK] << endl;
}

On pourrait dire mon "REF_TO" et "VALUE_OF"La sémantique est exagérée, mais elles étaient essentielles pour" un outwapping ".

J'ai fait ça "Variant"Chose avec neuf modèles différents, et mon actuel est le" plus lourd "(la plupart du code), mais celui que j'aime le mieux (presque le plus rapide avec une empreinte d'objet assez petite), et j'ai déprécié les huit autres designs pour mon utilisation.

Les "inconvénients" à ma conception sont:

  1. Les objets sont accessibles viastatic_cast<>() de void*(Sépreuve de type et assez rapide, mais l'indirection est requise; mais, l'effet secondaire est que la conception prend en charge le stockage de "null".)
  2. Les compiles sont plus longs en raison des types bien connus qui sont exposés à travers le Datum interface (mais vous pouvez utiliser DatumState Si vous ne voulez pas d'API de type bien connu).

Peu importe votre conception, je recommanderais ce qui suit:

  1. Utilisez un "enum"Ou quelque chose pour vous dire le"taper", séparé du"évaluer". (Je sais que tu peux les comprimer en un"int"Ou quelque chose avec un emballage bits, mais c'est lent pour l'accès et très difficile à maintenir à mesure que de nouveaux types sont introduits.)

  2. Appuyez sur des modèles ou quelque chose pour centraliser les opérations, avec un mécanisme de traitement spécifique au type (remplacement) (en supposant que vous souhaitez gérer les types non triviaux).

Le nom du match est "Maintenance simplifiée lors de l'ajout de nouveaux types" (ou du moins, c'était pour moi). Comme un article à terme, c'est une très bonne idée si vous réécrivez, réécrivez, réécrivez-vous, pour se maintenir Votre fonctionnalité comme vous retirer Le code requis pour maintenir le système (par exemple, minimiser l'effort requis pour adapter de nouveaux types à votre existant Variant Infrastructure).

Bonne chance!

Autres conseils

Fait quelque chose de similaire.

Vous pouvez ajouter un autre octet à «l'en-tête», indiquant le type de stockage vraiment.

Exemple dans un langage de programmation de style C:

typedef
enum VariantInternalType {
  vtUnassigned = 0;
  vtByte = 1;
  vtCharPtr = 2; // <-- "plain c" string
  vtBool = 3;
  // other supported data types
}

// --> real data
typedef
struct VariantHeader {
  void* Reserved; // <-- your data (byte or void*)
  VariantInternalType VariantInternalType;  
}

// --> hides real data
typedef
  byte[sizeof(VariantHeader)] Variant;

// allocates & assign a byte data type to a variant
Variant ByteToVar(byte value)
{
  VariantHeader MyVariantHeader;
  Variant MyVariant;

  MyVariantHeader.VariantInternalType = VariantInternalType.vtByte;
  MyVariantHeader.Reserved = value;  

  memcpy (&MyVariant, &MyVariantHeader, sizeof(Variant));

  return myVariant;
}

// allocates & assign a char array data type to a variant
Variant CharPtrToVar(char* value)
{
  VariantHeader MyVariantHeader;
  Variant MyVariant;

  MyVariantHeader.VariantInternalType = VariantInternalType.vtByte;
  MyVariantHeader.Reserved = strcpy(value);  

  // copy exposed struct type data to hidden array data
  memcpy(&MyVariant, &MyVariantHeader, sizeof(Variant));

  return myVariant;
}

// deallocs memory for any internal data type
void freeVar(Variant &myVariant)
{
  VariantHeader MyVariantHeader;

  // copy exposed struct type data to hidden array data
  memcpy(&MyVariantHeader, &MyVariant, sizeof(VariantHeader));

  switch (MyVariantHeader.VariantInternalType) {
    case vtCharPtr:
      strfree(MyVariantHeader.reserved);
    break;

    // other types

    default:
    break;
  }

  // copy exposed struct type data to hidden array data
  memcpy(&MyVariant, &MyVariantHeader, sizeof(Variant));
}

bool isVariantType(Variant &thisVariant, VariantInternalType thisType)
{
  VariantHeader MyVariantHeader;

  // copy exposed struct type data to hidden array data
  memcpy(&MyVariantHeader, &MyVariant, sizeof(VariantHeader));

  return (MyVariant.VariantInternalType == thisType);
}

// -------

void main()
{
  Variant myVariantStr = CharPtrToVar("Hello World");
  Variant myVariantByte = ByteToVar(42);

  char* myString = null;
  byte  myByte = 0;

  if isVariantType(myVariantStr, vtCharPtr) {
    myString = VarToCharPtr(myVariantStr);
    // print variant string into screen
  }

  // ...    
}

Ce n'est qu'une suggestion, et ce n'est pas testé.

Peut-être que vous avez déjà fait le calcul, mais la quantité de mémoire dont vous avez besoin pour la table de recherche n'est pas ce beaucoup.

Si vous avez juste besoin de vérifier si les types sont compatibles, vous avez besoin de (256 * 256) / 2 bits. Cela nécessite 4k de mémoire.

Si vous avez également besoin d'un pointeur vers une fonction de conversion, vous avez besoin de pointeurs (256 * 256) / 2. Cela nécessite 128k de mémoire sur une machine 32 bits et 256k sur une machine 64 bits. Si vous êtes prêt à faire une disposition d'adresse de bas niveau, vous pouvez probablement obtenir cela à 64k sur des machines 32 bits et 64 bits.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top