سؤال

كيف أجعل الوظيفة أدناه عامة لـ uint8_t، uint16_t، uint32_t، int8_t، int16_t، int32_t و float_t؟

لا أحب تكرار نفس المنطق في كل حالة كما ترون.والفرق الوحيد في كل حالة هو الصب.

أرغب بشكل مثالي في الحصول على حل يلتزم بمعيار C وبالتالي يكون محمولاً.أي أفكار هي موضع ترحيب.

شكرًا.

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;
}
هل كانت مفيدة؟

المحلول

كذلك أنت استطاع استخراج القوالب:

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
}

أو، إذا كانت الأنواع المختلفة تشكل نطاقًا متجاورًا من القيم من 0، فيمكنك إنشاء صفيفين من مؤشرات الوظائف والقيام بما يلي:

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

ملحوظات:

  • لا يزال يتعين عليك كتابة دالتين لكل نوع، على الرغم من أنه من السهل إنشاءهما بشكل كلي إذا كنت تفضل ذلك.
  • لا يبدو الأمر يستحق كل هذا العناء حقًا بالنسبة لهذا الرمز الصغير.
  • لقد اخترت int64_t نظرًا لأنه قادر على تمثيل جميع قيم جميع أنواع الأعداد الصحيحة التي تستخدمها، وبالتالي فإن التحويلات إليها int64_t لا تتجاهل أبدًا المعلومات ولا تغير أبدًا نتيجة المقارنة فيما يتعلق بإجراء نفس المقارنة في نوع المصدر.ولكن إذا كنت تريد أيضًا التغطية uint64_t, ، فلا يمكنك استخدام نفس النوع لكل شيء، نظرًا لعدم وجود نوع عدد صحيح يمكنه تمثيل جميع قيم جميع أنواع الأعداد الصحيحة.ستحتاج أيضًا إلى منفصل testLimitsf وظيفة ل float, وربما باستخدام long double كنوع شائع للمرونة المستقبلية.
  • [يحرر:لقد أدركت للتو، بافتراض IEEE-754، double في الواقع يمكن أن يمثل تمامًا جميع قيم جميع الأنواع التي تستخدمها.لذلك، مع وجود قيود طفيفة على قابلية النقل، يمكنك استخدامها testLimitsf لكل شيء والتعامل في الزوجي]
  • هل أنت متأكد من أنه يستحق التحويل إلى (على سبيل المثال) uint8_t قبل المقارنة؟إما أن تكون القيمة في النطاق لـ a uint8_t, ، وفي هذه الحالة لا تحتاج إلى التحويل، يمكنك فقط إجراء المقارنة.وإلا فإن القيمة ليست في النطاق، وفي هذه الحالة، يؤدي تقليل المعامل إلى جعل المقارنة لا معنى لها إلا في الحالات الخاصة 0 و-1.لذلك قد يكون الأمر يستحق ذلك، إذا كان هناك شيء لم تذكره يجعله كذلك، لكنه يبدو مريبًا بالنسبة لي.
  • لقد قلت في تعليق: "أحاول أن أجعل هذه الوظيفة أكثر كفاءة".وهذا قد يتعارض مع ذلك.من الممكن منطقيًا التضمين testLimits وكذلك المكالمات إلى وظائف الصب في switch, ، لكنني لن أعتمد على ذلك.

نصائح أخرى

لا توجد طريقة سهلة للقيام ببرمجة عامة مثل هذه في ج. إذا كنت قلقًا بشأن الصيانة ، فقد تكون هذه مناسبة نادرة حيث يكون الماكرو مناسبًا.

لماذا لا تستخدم ماكرو؟ Genacodicetagpre

ستضيف المراجعة التالية للمعيار (C1x) دعمًا للتعبيرات العامة من النوع (مثال من ويكيبيديا): Genacodicetagpre

يتوفر لدى دول مجلس التعاون الخليجي بعض دعم C1x الأولي.أعتقد أن _Generic لم يتم دعمه بعد ، ولكن ضع ذلك في اعتبارك للمستقبل.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top