اذهب إلى المحتوى

أهم السمات (Attributes) ومواضع استعمالها في Cpp


محمد بغات

السمة [[fallthrough]]

الإصدار ≥ C++‎ 17

عند إنهاء تعليمة case بكلمة switch ستنفَّذ الشيفرة التي تليها، وإن أردت منع هذا السلوك، فاستخدم تعليمة ‎‎break‎‎. يُسمّى هذا السلوك بـ "السقطة" (fallthrough)، وقد ينجم عنه أخطاء غير مقصودة، لذا تطلق العديد من المُصرِّفات والمحلّلات الساكنة (static analyzers) تحذيرًا منه.

وقد أُدخلت سمة قياسية (standard attribute) في C++ 17 من أجل الإشارة إلى أنّ التحذير ليس ضرويًا إذا كانت السقطة مقصودة في الشيفرة، ويمكن للمصرّفات إعطاء تحذيرات بأمان إذا أنهيت case بالكلمةُ المفتاحيةُ ‎break‎ أو ‎[[fallthrough]]‎، وكانت مؤلفة من تعليمة واحد على الأقل.

switch (input) {
case 2011:
case 2014:
case 2017:
    std::cout << "Using modern C++" << std::endl;
    [[fallthrough]]; // > لا يوجد تحذير
case 1998:
case 2003:
    standard = input;
}

راجع هذا الاقتراح لمزيد من الأمثلة حول كيفية استخدام ‎[[fallthrough]]‎.

السمة [[nodiscard]]

الإصدار ≥ C++‎ 17

يمكن استخدام السمة ‎[[nodiscard]]‎ للإشارة إلى أنّه لا ينبغي تجاهل القيمة المُعادة من دالة عند استدعاء دالة، ويجب على االمُصرِّف في حال تجاهلها إعطاء تحذير بهذا الشأن. يمكن إضافة هذه السمة إلى:

  • تعريف دالّة
  • نوع

ينتج عن إضافة السمة إلى نوع ما نفسُ السلوكِ الناتج عن إضافة السمة إلى كل الدوالّ التي تُعيد ذلك النوع.

template<typename Function>
[[nodiscard]] Finally<std::decay_t<Function>> onExit(Function &&f);
void f(int &i) {
    assert(i == 0);            // لجعل التعليقات واضحة
    ++i;                    // i == 1
    auto exit1 = onExit([&i]{ --i; });    //  f() التخفيض بـ 1 عند الخروج من
    ++i;                    // i == 2
    onExit([&i]{ --i; });            // خطأ: محاولة التخفيض بـ 1 مباشرة
// يُتوقع أن يصدر المصرف تنبيها
    std::cout << i << std::end;    // القيمة المتوقعة 2، لكن القيمة الحقيقية هي 1
}

راجع هذا الاقتراح لمزيد من الأمثلة حول كيفية استخدام ‎[[nodiscard]]‎.

السمة [[deprecated]] والسمة [[deprecated("reason")‎]]

الإصدار ≥ C++‎ 14

قدَّمت C++‎ 14 طريقة قياسية لإهمال (deprecating) الدوال عبر السمات. يمكن استخدام ‎[[deprecated]]‎ للإشارة إلى إهمال الدالّة. ويسمح التعبير ‎[[deprecated("reason")]]‎بتحديد سبب للإهمال يمكن أن نجعل المُصرِّف يظهِره.

void function(std::unique_ptr<A> &&a);
// توفير رسالة محدّدة تساعد المبرمجين الآخرين على تصحيح شيفراتهم
[[deprecated("Use the variant with unique_ptr instead, this function will be removed in the next release")]]
void function(std::auto_ptr<A> a);
// في حال عدم وجود رسالة، سيُطلق تنبيه عام عند الاستدعاء
[[deprecated]]
void function(A *a);

يُمكن تطبيق هذه السمة على:

  • تصريح صنف.
  • اسم typedef.
  • متغيّر.
  • حقل غير ساكن (non-static data member).
  • دالة.
  • تعداد (enum).
  • تخصيص قالب (template specialization).

(المرجع: c++14 standard draft: 7.6.5 Deprecated attribute)

السمة [[maybe_unused]]

تُستعمل السمة ‎[[maybe_unused]]‎ للدلالة على احتمال عدم استخدام منطق بعينه داخل الشيفرة، وترتبط غالبًا بشروط المعالج الأَولي (preprocessor). ويمكن استخدام هذه السمة لمنع هذا السلوك من خلال الإشارة إلى أنّ هذا الأمر مقصود، نظرًا لأنّ المُصرِّفات قد تبعث تحذيرات بشأن المتغيّرات غير المستخدمة.

وتُعد القيم المعادة التي تشير إلى نجاح عملية التنقيح من الأمثلة التطبيقية للمتغيّرات التي تُستخدم أثناء عمليات تنقيح بنيات الشيفرة (debug builds)، ولكن لا تبقى إليها حاجة في مرحلة الإنتاج. ويجب التحقق من شرط النجاح أثناء تنقيح أخطاء البُنيات، رغم أن تلك التحققات ستُحذف في مرحلة الإنتاج لعدم الحاجة إليها.

[[maybe_unused]] auto mapInsertResult = configuration.emplace("LicenseInfo",
stringifiedLicenseInfo);
assert(mapInsertResult.second); // لا تُستدعى إلا خلال الإطلاق، لذا لا ينبغي أن تكون في القاموس

هناك مثال أكثر تعقيدًا، وهي الدوال المساعدة التي توضع في فضاء اسم غير مُسمّى (unnamed namespace)، وتلك الدوال إن لم تُستخدَم أثناء التصريف، فقد يطلق االمُصرِّف تحذيرًا بهذا الشأن، لذا قد ترغب في حمايتها باستخدام نفس وسوم المعالج الأولي التي تُستخدَم مع المُستدعي (caller)، لكن قد يكون هذا معقدًا، لهذا يُعدُّ استخدام السمة ‎[[maybe_unused]]‎ بديلاً أسهل في الصيانة.

namespace {
    [[maybe_unused]] std::string createWindowsConfigFilePath(const std::string &relativePath);
    // TODO: …  BSD, MAC أعد استخدام هذا على
    [[maybe_unused]] std::string createLinuxConfigFilePath(const std::string &relativePath);
}
std::string createConfigFilePath(const std::string &relativePath) {
#if OS == "WINDOWS"
    return createWindowsConfigFilePath(relativePath);
#elif OS == "LINUX"
    return createLinuxConfigFilePath(relativePath);
#else
#error "OS is not yet supported"
#endif
}

راجع هذا الاقتراح لمزيد من الأمثلة حول كيفية استخدام ‎[[maybe_unused]]‎.

السمة [[noreturn]]

الإصدار ≥ C++‎ 11

قدّمت C++‎ 11 سمةَ ‎[[noreturn]]‎ التي يمكن استخدامها مع دالّة للإشارة إلى أنّ تلك الدالّة لا تُعاد إلى المُستدعي، سواء عبر تعليمة return، أو عند الوصول إلى نهاية متنها -من المهم الإشارة إلى أن هذا لا ينطبق على الدوال الفارغة ‎void‎، نظرًا لأنّها تعود إلى المُستدعي، ولكن لا تعيد أيّ قيمة-. قد تنتهي مثل هذه الدوالّ عبر استدعاء ‎std::terminate‎ أو ‎std::exit‎، أو عبر رفع اعتراض (throwing an exception). تجدر الإشارة أيضًا إلى أنّ مثل هذه الدوالّ يمكن أن تُعاد عبر تنفيذ ‎longjmp‎.

على سبيل المثال، الدالّة أدناه إمّا أن ترفع اعتراضًا أو تستدعي ‎std::terminate‎، لذلك فهي مرشح جيد لاستخدام ‎[[noreturn]]‎:

[[noreturn]] void ownAssertFailureHandler(std::string message) {
    std::cerr << message << std::endl;
    if (THROW_EXCEPTION_ON_ASSERT)
        throw AssertException(std::move(message));
    std::terminate();
}

يسمح هذا السلوك للمُصرِّف بإنهاء الدوال التي لا تحتوي على تعليمة return إذا كان يعلم أنّ الشيفرة لن تُنفَّذ. هنا، لن يحتاج المُصرِّف إلى إضافة الشيفرة الموجودة أسفل ذلك الاستدعاء نظرًا لأنّ استدعاء ‎ownAssertFailureHandler‎ في الشيفرة أدناه لن يعود أبدًا:

std::vector < int > createSequence(int end) {
    if (end > 0) {
        std::vector < int > sequence;
        sequence.reserve(end + 1);
        for (int i = 0; i <= end; ++i)
            sequence.push_back(i);
        return sequence;
    }
    ownAssertFailureHandler("Negative number passed to createSequence()"s);
    // return std::vector<int>{}; //< Not needed because of [[noreturn]]
}

سيكون السلوك غير معرَّف إذا كانت الدالّة ستعاد، ونتيجة لذلك لا يُسمح بما يلي:

[[noreturn]] void assertPositive(int number) {
    if (number >= 0)
        return;
    else
        ownAssertFailureHandler("Positive number expected"s); //< [[noreturn]]
}

لاحظ أنّ ‎[[noreturn]]‎ تُستخدم في الغالب في الدوال الفارغة، لكنها ليست ضرورية، وهذا يسمح باستخدام الدوال في البرمجة العامة (generic programming):

template<class InconsistencyHandler>
double fortyTwoDivideBy(int i) {
    if (i == 0)
        i = InconsistencyHandler::correct(i);
    return 42. / i;
}
struct InconsistencyThrower {
    static [[noreturn]] int correct(int i) { ownAssertFailureHandler("Unknown inconsistency"s); }
}
struct InconsistencyChangeToOne {
    static int correct(int i) { return 1; }
}
double fortyTwo = fortyTwoDivideBy<InconsistencyChangeToOne>(0);
double unreachable = fortyTwoDivideBy<InconsistencyThrower>(0);

تحتوي دوال المكتبة القياسية التالية على هذه السمات:

  • std::abort
  • std::exit
  • std::quick_exit
  • std::unexpected
  • std::terminate
  • std::rethrow_exception
  • std::throw_with_nested
  • std::nested_exception::rethrow_nested

هذا الدرس جزء من سلسلة دروس عن C++‎.

ترجمة -بتصرّف- للفصل Chapter 123: Attributes من كتاب C++ Notes for Professionals


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...