Frage

Wie mache ich die folgende Funktion generisch für uint8_t, uint16_t, uint32_t, int8_t, int16_t, int32_t und float_t?

Ich mag es nicht, in jedem Fall dieselbe Logik zu wiederholen, wie Sie sehen können.Der einzige Unterschied ist jeweils das Casting.

Ich hätte idealerweise eine Lösung, die dem C-Standard entspricht und daher portabel ist.Ideen sind willkommen.

Danke.

static bool_t IsWithinLimits(const dbKey_t *key, const void *data)
{
    bool_t isWithinLimits = TRUE;
    limits_t limits = getDefinedLimits(key);

    switch(key->type)
    {
      case TYPE_UINT8:
        if((*(const UINT8*)data > (UINT8)limits.max) || (*(const UINT8*)data < (UINT8)limits.min))
        {
          isWithinLimits = FALSE;
        }
        break;

      case TYPE_UINT16:
        if((*(UINT16*)pData > (UINT16)limits.max) || (*(UINT16*)data < (UINT16)limits.min))
        {
          isWithinLimits = FALSE;
        }
        break;

      case TYPE_UINT32:
       ...
       break;

      case TYPE_INT8:
       ...
       break;

      case TYPE_INT16:
       ...
       break;

      case TYPE_INT32:
       ...
       break;

      case TYPE_FLOAT:
       ...
       break;
    }

  return isWithinLimits;
}

War es hilfreich?

Lösung

Nun, Sie könnten die Darsteller extrahieren:

int64_t loadptr_uint8(const void *p)  {
    return *(uint8_t*)p;
}
int64_t convert_uint8(int64_t val) {
    return (uint8_t)val;
}

int testLimits(const limits_t *plimits, const void *pData, int64_t(*loadptr)(void*), int64_t (*convert)(int64_t)) {
    return loadptr(pData) <= convert(limits->max) && loadptr(pData) >= convert(limits->min);
}

switch(key->type) {
    case TYPE_UINT8:
        isWithinLimits = testLimits(&limits, pData, loadptr_uint8, convert_uint8);
        break;
    // etc
}

Wenn die verschiedenen Typen einen zusammenhängenden Wertebereich von 0 bilden, können Sie sogar zwei Arrays von Funktionszeigern erstellen und Folgendes tun:

bool isWithinLimits = testLimits(&limits, pData, loadptrs[key->type], converts[key->type]);

Hinweise:

  • Sie müssen noch zwei Funktionen für jeden Typ schreiben, obwohl diese auf Wunsch leicht makrogeneriert werden können.
  • Für diesen kleinen Code scheint es sich nicht wirklich zu lohnen.
  • Ich habe int64_t gewählt, da es alle Werte aller von Ihnen verwendeten Integer-Typen darstellen kann. Daher werden bei der Konvertierung in int64_t niemals Informationen verworfen und das Ergebnis eines Vergleichs in Bezug auf den gleichen Vergleich in nie geändert der Quelltyp. Wenn Sie jedoch auch uint64_t behandeln möchten, können Sie nicht für alle denselben Typ verwenden, da es keinen Integer-Typ gibt, der alle Werte aller Integer-Typen darstellen kann. Sie benötigen außerdem eine separate testLimitsf-Funktion für float, wobei Sie möglicherweise long double als allgemeinen Typ für zukünftige Flexibilität verwenden.
  • [Bearbeiten: Ich habe gerade festgestellt, dass double unter der Annahme von IEEE-754 tatsächlich alle Werte aller von Ihnen verwendeten Typen genau darstellen kann. Mit einer geringfügigen Einschränkung der Portabilität können Sie also testLimitsf für alles verwenden und im Doppel handeln.]
  • Sind Sie sicher, dass es sich lohnt, vor dem Vergleich in (zum Beispiel) uint8_t zu konvertieren? Entweder liegt der Wert im Bereich für einen uint8_t. In diesem Fall müssen Sie nicht konvertieren, sondern können nur den Vergleich durchführen. Andernfalls liegt der Wert nicht im Bereich. In diesem Fall macht die Modulo-Reduzierung den Vergleich etwas bedeutungslos, außer in den Sonderfällen 0 und -1. Es könnte sich also lohnen, wenn etwas, das Sie nicht angegeben haben, es so macht, aber es sieht für mich faul aus.
  • Sie sagten in einem Kommentar: "Ich versuche, diese Funktion effizienter zu gestalten." Dies könnte dagegen sprechen. Es ist logisch möglich, testLimits und auch die Aufrufe der Casting-Funktionen im switch zu inline, aber ich würde nicht damit rechnen.

Andere Tipps

In C gibt es keine einfache Möglichkeit, eine generische Programmierung wie diese durchzuführen. Wenn Sie sich Sorgen über die Wartung machen, ist dies möglicherweise eine seltene Gelegenheit, in der ein Makro geeignet ist.

Warum nicht ein Makro verwenden?

#define DO_MY_WORK(TYPE)\
  if((*(TYPE*)pData > (TYPE)tLimits.zMax) || (*(TYPE*)pData < (TYPE)tLimits.zMin))\
  {\
      isWithinLimits = FALSE;\
  }

Die nächste Überarbeitung des Standards (C1x) wird Unterstützung für generische Typausdrücke hinzufügen (Beispiel aus Wikipedia):

#define cbrt(X) _Generic((X), long double: cbrtl, \
                              default: cbrt, \
                              float: cbrtf)(X)

gcc bietet vorläufige C1x-Unterstützung.Ich denke, _Generic wird noch nicht unterstützt, aber denken Sie daran für die Zukunft.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top