الاستثناءات الحتمية ومعالجة الأخطاء في "C ++ للمستقبل"


من الغريب أنه لم يتم ذكر حبري حتى الآن حول الاقتراح الصاخب لمعيار C ++ المسمى "الاستثناءات الحتمية الصفرية". تصحيح هذا الإغفال المزعج.


إذا كنت قلقًا بشأن النفقات العامة للاستثناءات ، أو كان عليك تجميع الشفرة دون دعم الاستثناء ، أو مجرد التساؤل عما سيحدث في معالجة الخطأ في C ++ 2b (مرجع إلى منشور حديث ) ، أطلب القط. أنت تنتظر ضغطًا من كل شيء يمكن العثور عليه الآن حول الموضوع ، واثنين من استطلاعات الرأي.


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


double safe_divide(int x, int y) throws(arithmetic_error) { if (y == 0) { throw arithmetic_error::divide_by_zero; } else { return as_double(x) / y; } } void caller() noexcept { try { cout << safe_divide(5, 2); } catch (arithmetic_error e) { cout << e; } } 

إذا كان نوع الخطأ المحدد غير مهم / غير معروف ، فيمكنك ببساطة استخدام throws catch (std::error e) .


جيد أن تعرف


std::optional و std::expected


دعونا نقرر أن الخطأ الذي يمكن أن ينشأ في الوظيفة ليس "قاتلاً" بما يكفي لإلقاء استثناء لها. تقليديا ، يتم إرجاع معلومات الخطأ باستخدام معلمة خارج. على سبيل المثال ، يوفر Filesystem TS عددًا من الميزات المتشابهة:


 uintmax_t file_size(const path& p, error_code& ec); 

(لا ترمي استثناءً لأنه لم يتم العثور على الملف؟) ومع ذلك ، فإن معالجة رمز الخطأ مرهقة وعرضة للأخطاء. من السهل نسيان رمز الخطأ للتحقق. تمنع أنماط التعليمات البرمجية الحديثة استخدام معلمات الإخراج ؛ بدلاً من ذلك ، يوصى بإرجاع بنية تحتوي على النتيجة بأكملها.


لبعض الوقت الآن ، تقدم Boost حلاً أنيقًا للتعامل مع مثل هذه الأخطاء "غير المميتة" ، والتي يمكن أن تحدث في سيناريوهات معينة في البرنامج الصحيح بالمئات:


 expected<uintmax_t, error_code> file_size(const path& p); 

النوع expected مشابه variant ، لكنه يوفر واجهة ملائمة للعمل مع "النتيجة" و "الخطأ". بشكل افتراضي ، يتم تخزين النتيجة expected في expected . قد يبدو تنفيذ file_size :


 file_info* info = read_file_info(p); if (info != null) { uintmax_t size = info->size; return size; // <== } else { error_code error = get_error(); return std::unexpected(error); // <== } 

إذا كان سبب الخطأ غير مثير للاهتمام بالنسبة لنا ، أو يمكن أن يتكون الخطأ فقط في "غياب" النتيجة ، فيمكن استخدام optional :


 optional<int> parse_int(const std::string& s); optional<U> get_or_null(map<T, U> m, const T& key); 

في C ++ 17 من Boost ، جاء اختياري إلى std (بدون دعم optional<T&> ) ؛ في C ++ 20 ، قد يضيفون المتوقع (هذا اقتراح فقط ، شكرًا RamzesXI للتصحيح).


العقود


العقود (يجب عدم الخلط بينها وبين المفاهيم) هي طريقة جديدة لفرض قيود على معلمات الوظائف ، تمت إضافتها في C ++ 20. تمت إضافة 3 تعليقات توضيحية:


  • يتوقع الشيكات معلمات الدالة
  • يضمن التحقق من القيمة المرجعة للدالة (يأخذها كوسيطة)
  • تأكيد - بديل حضاري للتأكيد الكلي

 double unsafe_at(vector<T> v, size_t i) [[expects: i < v.size()]]; double sqrt(double x) [[expects: x >= 0]] [[ensures ret: ret >= 0]]; value fetch_single(key e) { vector<value> result = fetch(vector<key>{e}); [[assert result.size() == 1]]; return v[0]; } 

يمكنك تكوين خرق العقد:


  • يسمى السلوك غير المحدد
  • فحص واستدعى خروج المستخدم ، وبعد ذلك std::terminate

من المستحيل الاستمرار في تشغيل البرنامج بعد خرق العقد ، لأن المترجمين يستخدمون ضمانات من العقود لتحسين كود الوظيفة. إذا كان هناك أدنى شك في أن العقد سيتم الوفاء به ، فمن الجدير إضافة شيك إضافي.


الأمراض المنقولة جنسيا :: error_code


تسمح لك مكتبة <system_error> ، المُضافة في C ++ 11 ، بتوحيد معالجة رموز الخطأ في برنامجك. يتكون std :: error_code من رمز خطأ من النوع int ومؤشر للكائن من بعض فئة السلالة std :: error_category . هذا الكائن ، في الواقع ، يلعب دور جدول الوظائف الافتراضية ويحدد سلوك رمز std::error_code .


لإنشاء std::error_code الخاص بك ، يجب عليك تحديد std::error_category سلالة std::error_category وتنفيذ الأساليب الافتراضية ، وأهمها:


 virtual std::string message(int c) const = 0; 

يجب عليك أيضًا إنشاء متغير عام لفئة std::error_category الخاصة بك. يبدو أن معالجة الخطأ باستخدام error_code + المتوقع شيء مثل هذا:


 template <typename T> using result = expected<T, std::error_code>; my::file_handle open_internal(const std::string& name, int& error); auto open_file(const std::string& name) -> result<my::file> { int raw_error = 0; my::file_handle maybe_result = open_internal(name, &raw_error); std::error_code error{raw_error, my::filesystem_error}; if (error) { return unexpected{error}; } else { return my::file{maybe_result}; } } 

من المهم أنه في std::error_code القيمة 0 تعني عدم وجود خطأ. إذا لم يكن هذا هو الحال بالنسبة لرموز الخطأ الخاصة بك ، فقبل تحويل رمز خطأ النظام إلى std::error_code ، يجب عليك استبدال الرمز 0 بـ SUCCESS والعكس صحيح.


يتم وصف جميع رموز أخطاء النظام في errc و system_category . إذا أصبحت إعادة التوجيه اليدوي لرموز الخطأ في مرحلة معينة كئيبة للغاية ، فيمكنك دائمًا التفاف رمز الخطأ في std::system_error والتخلص منه.


تحرك تدميري / سهل الانتقال


تتيح لك إنشاء فئة أخرى من الكائنات التي تمتلك بعض الموارد. على الأرجح ، ستحتاج إلى جعلها غير قابلة للنسخ ، ولكنها قابلة للنقل ، لأنه من غير المناسب العمل مع الأشياء غير المنقولة (قبل C ++ 17 لا يمكن إرجاعها من وظيفة).


ولكن هنا تكمن المشكلة: على أي حال ، يجب حذف الكائن المنقول. لذلك ، من الضروري وجود حالة خاصة "نقل من" ، أي كائن "فارغ" لا يحذف أي شيء. اتضح أن كل فئة C ++ يجب أن يكون لها حالة فارغة ، أي أنه من المستحيل إنشاء فئة مع (ضمان) ثابت من الصواب ، من المنشئ إلى المدمر. على سبيل المثال ، لا يمكن إنشاء فئة open_file الصحيحة لملف مفتوح طوال حياته. من الغريب ملاحظة ذلك بإحدى اللغات القليلة التي تستخدم RAII بنشاط.


مشكلة أخرى هي التصفير للكائنات القديمة عند النقل يضيف std::vector<std::unique_ptr<T>> : ملء std::vector<std::unique_ptr<T>> يمكن أن يكون أبطأ مرتين من std::vector<T*> بسبب كومة التصفير للمؤشرات القديمة عند التحرك تليها إزالة الدمى.


يميل مطورو C ++ منذ فترة طويلة إلى Rust ، حيث لا يتم استدعاء المدمرات على الكائنات المنقولة. تسمى هذه الميزة الحركة التدميرية. لسوء الحظ ، لا يعرض Proposal Trivially Relocatable إضافته إلى C ++. ولكن سيتم حل مشكلة النفقات العامة.


تعتبر الفئة قابلة للنقل بشكل تافه إذا كانت عمليتان: نقل وحذف الكائن القديم مكافئ لـ memcpy من الكائن القديم إلى الكائن الجديد. لم يتم حذف الكائن القديم ، يسميه المؤلفون "إسقاطه على الأرض".


النوع قابل للنقل بشكل تافه من وجهة نظر المترجم إذا تحقق أحد الشروط (العودية) التالية:


  1. إنه قابل للحركة بشكل تافه + قابل للتلف بشكل تافه (مثل بنية int أو POD)
  2. هذه هي الفئة المميزة [[trivially_relocatable]]
  3. هذه فئة جميع أعضائها يمكن نقلهم بسهولة.

يمكنك استخدام هذه المعلومات مع std::uninitialized_relocate ، الذي ينفذ نقل init + حذف بالطريقة المعتادة ، أو يتم تسريعه إن أمكن. يُقترح وضع علامة على [[trivially_relocatable]] معظم أنواع المكتبة القياسية ، بما في ذلك std::string و std::vector و std::unique_ptr . النفقات العامة std::vector<std::unique_ptr<T>> مع وضع هذا في الاعتبار ، سيختفي الاقتراح.


ما هو الخطأ في الاستثناءات الآن؟


تم تطوير آلية استثناء C ++ في عام 1992. تم اقتراح خيارات تنفيذ مختلفة. من بين هذه ، تم اختيار آلية جدول الاستثناءات التي تضمن عدم وجود عبء إضافي للمسار الرئيسي لتنفيذ البرنامج. لأنه منذ لحظة إنشائها ، كان من المفترض أن يتم طرح الاستثناءات في حالات نادرة جدًا .


مساوئ الاستثناءات الديناميكية (أي العادية):


  1. في حالة الاستثناء الذي تم طرحه ، يبلغ الحمل في المتوسط ​​حوالي 10000-100.000 دورة وحدة المعالجة المركزية ، وفي أسوأ الحالات ، يمكن أن تصل إلى ترتيب المللي ثانية
  2. زيادة حجم الملف الثنائي بنسبة 15-38٪
  3. عدم التوافق مع واجهة برمجة C
  4. استثناء ضمني يرمي الدعم في جميع الوظائف باستثناء noexcept . يمكن طرح استثناء في أي مكان تقريبًا في البرنامج ، حتى عندما لا يتوقع مؤلف الوظيفة ذلك

بسبب هذه العيوب ، فإن نطاق الاستثناءات محدود بشكل كبير. عندما لا يمكن تطبيق الاستثناءات:


  1. عندما تكون الحتمية مهمة ، أي حيث يكون من غير المقبول أن يعمل الرمز "أحيانًا" 10 ، 100 ، 1000 مرة أبطأ من المعتاد
  2. عندما لا تكون مدعومة في ABI ، على سبيل المثال ، في المتحكمات الدقيقة
  3. عندما تتم كتابة جزء كبير من التعليمات البرمجية في C
  4. في الشركات التي لديها عدد كبير من الرموز القديمة ( Google Style Guide ، Qt ). إذا كانت هناك وظيفة واحدة على الأقل غير آمنة للاستثناء في الشفرة ، فعندئذٍ وفقًا لقانون اللغط ، سيتم طرح استثناء من خلاله عاجلاً أو آجلاً وإنشاء خطأ
  5. في الشركات التي توظف المبرمجين الذين ليس لديهم فكرة عن سلامة الاستثناء

وفقًا للاستطلاعات ، في أماكن العمل الخاصة بمطوري 52٪ (!) ، تحظر قواعد الشركات الاستثناءات.


لكن الاستثناءات جزء لا يتجزأ من C ++! من خلال تضمين علامة -fno-exceptions ، يفقد المطورون القدرة على استخدام جزء كبير من المكتبة القياسية. وهذا يحرض الشركات على إنشاء "مكتبات قياسية" خاصة بهم ، ونعم ، لابتكار فئة الأوتار الخاصة بهم.


لكن هذه ليست النهاية. الاستثناءات هي الطريقة القياسية الوحيدة لإلغاء إنشاء كائن في المنشئ وإلقاء خطأ. عندما يتم إيقاف تشغيلها ، تظهر رجسة مثل التهيئة على مرحلتين. لا يمكن للمشغلين أيضًا استخدام رموز الخطأ ، لذلك يتم استبدالهم بوظائف مثل assign .


اقتراح: استثناءات المستقبل


آلية نقل استثناء جديدة


وصف Herb Sutter في P709 آلية نقل استثناء جديدة. من حيث المبدأ ، تقوم الدالة بإرجاع std::expected ، ومع ذلك ، بدلاً من تمييز منفصل من نوع bool ، والذي سيشغل مع المحاذاة ما يصل إلى 8 بايت على المكدس ، يتم نقل هذا الجزء من المعلومات بطريقة أسرع ، على سبيل المثال ، إلى Carry Flag.


ستحصل الوظائف التي لا تلمس CF (معظمها) على فرصة لاستخدام الاستثناءات الثابتة مجانًا - سواء في حالة الإرجاع العادي ، أو في حالة الاستثناء! سوف تتلقى الوظائف التي يتم حفظها واستعادتها الحد الأدنى من النفقات العامة ، وستظل أسرع من std::expected وأي رموز خطأ عادية.


تبدو الاستثناءات الثابتة كما يلي:


 int safe_divide(int i, int j) throws(arithmetic_errc) { if (j == 0) throw arithmetic_errc::divide_by_zero; if (i == INT_MIN && j == -1) throw arithmetic_errc::integer_divide_overflows; return i / j; } double foo(double i, double j, double k) throws(arithmetic_errc) { return i + safe_divide(j, k); } double bar(int i, double j, double k) { try { cout << foo(i, j, k); } catch (erithmetic_errc e) { cout << e; } } 

في النسخة البديلة ، يُقترح إلزام الكلمة الأساسية try في نفس التعبير مثل استدعاء دالة throws : try i + safe_divide(j, k) . سيؤدي ذلك إلى تقليل عدد حالات استخدام وظائف throws في التعليمات البرمجية غير الآمنة للاستثناءات إلى الصفر تقريبًا. على أي حال ، على عكس الاستثناءات الديناميكية ، ستكون IDE قادرة على إبراز التعبيرات التي ترمي الاستثناءات بطريقة أو بأخرى.


حقيقة أن الاستثناء الذي تم طرحه لا يتم تخزينه بشكل منفصل ، ولكن يتم وضعه مباشرة في مكان القيمة المرتجعة ، يفرض قيودًا على نوع الاستثناء. أولاً ، يجب أن يتم نقلها بشكل تافه. ثانيًا ، يجب ألا يكون حجمه كبيرًا جدًا (ولكن يمكن أن يكون شيئًا مثل std::unique_ptr ) ، وإلا ستحتفظ جميع الوظائف بمساحة أكبر على المكدس.


رمز الحالة


<system_error2> مكتبة <system_error2> ، التي طورها Niall Douglas ، على status_code<T> - error_code "الجديد ، الأفضل". الاختلافات الرئيسية عن error_code :


  1. status_code - نوع نموذج يمكن استخدامه لتخزين أي رموز خطأ يمكن تصورها تقريبًا (جنبًا إلى جنب مع مؤشر إلى status_code_category ) ، دون استخدام الاستثناءات الثابتة
  2. يجب أن يكون T قابلاً للانتقال والنسخ بشكل تافه (الأخير ، IMHO ، لا يجب أن يكون إلزامياً). عند النسخ والحذف ، يتم استدعاء الوظائف الافتراضية من status_code_category
  3. لا يمكن تخزين status_code فقط بيانات الخطأ ، ولكن أيضًا معلومات إضافية حول عملية مكتملة بنجاح
  4. لا تقوم الدالة code.message() " code.message() الافتراضية بإرجاع std::string ، لكن string_ref هي نوع سلسلة ثقيلة نوعًا ما ، وهي std::string_view . هناك يمكنك string_view أو string أو std::shared_ptr<string> ، أو طريقة مجنونة أخرى لامتلاك سلسلة. يدعي نيال أن #include <string> سيجعل الرأس <system_error2> غير مقبول "ثقيل"

بعد ذلك ، تم errored_status_code<T> - مجمّع فوق errored_status_code<T> مع المُنشئ التالي:


 errored_status_code(status_code<T>&& code) [[expects: code.failure() == true]] : code_(std::move(code)) {} 

خطأ


نوع الاستثناء الافتراضي ( throws بدون نوع) ، بالإضافة إلى النوع الأساسي من الاستثناءات التي يلقي بها جميع الآخرين (مثل std::exception ) ، هو error . يتم تعريف شيء مثل هذا:


 using error = errored_status_code<intptr_t>; 

بمعنى أن error هو رمز الحالة "خطأ" ، حيث يتم وضع القيمة ( value ) في مؤشر واحد. نظرًا لأن آلية status_code_category تضمن الحذف والحركة والنسخ بشكل صحيح ، نظريًا ، يمكن حفظ أي بنية بيانات عن طريق error . عمليًا ، سيكون هذا أحد الخيارات التالية:


  1. الأعداد الصحيحة (int)
  2. std::exception_handle ، أي مؤشر إلى استثناء ديناميكي تم طرحه
  3. status_code_ptr ، على سبيل المثال ، unique_ptr إلى unique_ptr التعسفي status_code<T> .

تكمن المشكلة في أن الحالة 3 لا يتم التخطيط لإعطاء الفرصة لإعادة error إلى status_code<T> . الشيء الوحيد الذي يمكنك فعله هو الحصول على message() status_code<T> . حتى تتمكن من استعادة القيمة مرة أخرى في error مرة أخرى ، قم برميها كاستثناء ديناميكي (!) ، ثم التقطها ولفها error . بشكل عام ، يعتقد نيال أنه يجب تخزين رموز الخطأ ورسائل السلسلة فقط عن طريق error ، وهو ما يكفي لأي برنامج.


للتمييز بين أنواع الأخطاء المختلفة ، يُقترح استخدام عامل المقارنة "الظاهري":


 try { open_file(name); } catch (std::error e) { if (e == filesystem_error::already_exists) { return; } else { throw my_exception("Unknown filesystem error, unable to continue"); } } 

سيفشل استخدام كتل التقاط متعددة أو dynamic_cast لتحديد نوع الاستثناء!


التفاعل مع الاستثناءات الديناميكية


قد تحتوي الوظيفة على أحد المواصفات التالية:


  • noexcept : لا يوجد استثناءات
  • throws(E) : يلقي الاستثناءات الثابتة فقط
  • (لا شيء): يلقي استثناءات ديناميكية فقط

throws يعني noexcept . إذا تم طرح استثناء ديناميكي من دالة "ثابتة" ، يتم لفه في error . إذا تم طرح استثناء ثابت من دالة "ديناميكية" ، يتم لفه في استثناء status_error . مثال:


 void foo() throws(arithmetic_errc) { throw erithmetic_errc::divide_by_zero; } void bar() throws { //  arithmetic_errc   intptr_t //     error foo(); } void baz() { // error    status_error bar(); } void qux() throws { // error    status_error baz(); } 

الاستثناءات في C؟!


ينص الاقتراح على إضافة استثناءات إلى أحد معايير C المستقبلية ، وستكون هذه الاستثناءات متوافقة مع ABI مع الاستثناءات الثابتة C ++. بنية مشابهة لـ std::expected<T, U> ، سيتعين على المستخدم الإعلان بشكل مستقل ، على الرغم من أنه يمكن إزالة التكرار باستخدام وحدات الماكرو. يتكون بناء الجملة من (من أجل البساطة ، سنفترض ذلك) فشل الكلمات الرئيسية ، الفشل ، الصيد.


 int invert(int x) fails(float) { if (x != 0) return 1 / x; else return failure(2.0f); } struct expected_int_float { union { int value; float error; }; _Bool failed; }; void caller() { expected_int_float result = catch(invert(5)); if (result.failed) { print_error(result.error); return; } print_success(result.value); } 

في الوقت نفسه ، في C ++ سيكون من الممكن أيضًا استدعاء وظائف fails من C ، والإعلان عنها في كتل extern C . وبالتالي ، في C ++ سيكون هناك مجموعة كاملة من الكلمات الرئيسية للعمل مع الاستثناءات:


  • throw() - تمت إزالته في C ++ 20
  • noexcept - محدد الوظيفة ، الوظيفة لا ترمي استثناءات ديناميكية
  • noexcept(expression) - محدد الوظيفة ، لا تقوم الدالة noexcept(expression) استثناءات ديناميكية
  • noexcept(expression) - هل يرمي التعبير استثناءات ديناميكية؟
  • throws(E) - محدد الوظيفة ، تقوم الدالة بطرح استثناءات ثابتة
  • throws = throws(std::error)
  • fails(E) - تقوم دالة مستوردة من C بطرح استثناءات ثابتة

لذلك ، في C ++ ، أحضروا (أو بالأحرى ، سلموا) عربة من الأدوات الجديدة لمعالجة الأخطاء. بعد ذلك ، يطرح سؤال منطقي:


متى تستخدم ماذا؟


الاتجاه العام


تنقسم الأخطاء إلى عدة مستويات:


  • أخطاء مبرمج. معالجتها باستخدام العقود. أنها تؤدي إلى جمع السجلات وإنهاء البرنامج وفقا لمفهوم الفشل السريع . أمثلة: مؤشر فارغ (عندما يكون هذا غير صالح) ؛ القسمة على صفر ؛ أخطاء تخصيص الذاكرة التي لم يتوقعها المبرمج.
  • الأخطاء الفادحة التي قدمها المبرمج. يتم طرحها مليون مرة أقل من العائد العادي من دالة ، مما يجعل استخدام الاستثناءات الديناميكية له ما يبرره. عادة ، في مثل هذه الحالات ، تحتاج إلى إعادة تشغيل النظام الفرعي بأكمله للبرنامج أو إعطاء خطأ عند تنفيذ العملية. أمثلة: فقد الاتصال بقاعدة البيانات فجأة ؛ أخطاء تخصيص الذاكرة المقدمة من المبرمج.
  • أخطاء قابلة للاسترداد عندما يمنع شيء ما الوظيفة من إكمال مهمتها ، ولكن قد تعرف وظيفة الاستدعاء ما يجب فعله بها. تمت معالجتها بواسطة الاستثناءات الثابتة. أمثلة: العمل مع نظام الملفات ؛ أخطاء الإدخال / الإخراج الأخرى ؛ بيانات المستخدم غير صحيحة vector::at() .
  • أكملت الوظيفة مهمتها بنجاح ، وإن كان ذلك بنتيجة غير متوقعة. std::variant std::optional ، std::expected ، std::variant . أمثلة: stoi() ؛ vector::find() ؛ map::insert .

في المكتبة القياسية ، من الأكثر موثوقية التخلي تمامًا عن استخدام الاستثناءات الديناميكية من أجل جعل التجميع "بدون استثناءات" قانونيًا.


تخطئ


يجب استبدال الوظائف التي تستخدم errno للعمل بسرعة وسهولة مع رموز الخطأ C و C ++ fails(int) throws(std::errc) ، على التوالي. لبعض الوقت ، ستتعايش الإصدارات القديمة والجديدة من وظائف المكتبة القياسية ، ثم سيتم الإعلان عن القديم القديم.


نفاد الذاكرة


يتم التعامل مع أخطاء تخصيص الذاكرة عن طريق الخطاف العام new_handler ، والذي يمكنه:


  1. القضاء على نقص الذاكرة ومواصلة التنفيذ
  2. رمي استثناء
  3. برنامج التصادم

الآن std::bad_alloc طرح std::bad_alloc افتراضيًا. يقترح استدعاء std::terminate() بشكل افتراضي. إذا كنت بحاجة إلى السلوك القديم ، فاستبدل المعالج بالآخر الذي تحتاجه في بداية main() .


ستصبح جميع الوظائف الحالية للمكتبة القياسية غير noexcept البرنامج عند std::bad_alloc . في الوقت نفسه ، سيتم إضافة واجهات برمجة تطبيقات جديدة مثل vector::try_push_back ، مما يسمح بأخطاء تخصيص الذاكرة.


logic_error


الاستثناءات std::logic_error ، std::logic_error ، std::domain_error ، std::invalid_argument std::length_error ، std::out_of_range ، std::future_error تقرير عن انتهاك شرط مسبق للدالة. يجب أن يستخدم نموذج الخطأ الجديد العقود بدلاً من ذلك. لن يتم تجاهل أنواع الاستثناءات المدرجة ، ولكن سيتم استبدال جميع حالات استخدامها تقريبًا في المكتبة القياسية بـ [[expects: …]] .


حالة الاقتراح الحالي


الاقتراح الآن في حالة مسودة. لقد تغير الكثير بالفعل ، ولا يزال بإمكانه تغيير الكثير. لم تنجح بعض التطورات في النشر ، لذا فإن واجهة برمجة التطبيقات المقترحة <system_error2> ليست ذات صلة تمامًا.


تم وصف الاقتراح في 3 وثائق:


  1. P709 - المستند الأصلي من شعار سوتر
  2. P1095 - استثناءات محددة في رؤية نيال دوغلاس ، تغيرت بعض اللحظات ، إضافة توافق لغة سي
  3. P1028 - API من تطبيق الاختبار std::error

لا يوجد حاليا مترجم يدعم الاستثناءات الثابتة. وبناء على ذلك ، ليس من الممكن بعد وضع معاييرهم.


C++23. , , , C++26, , , .


الخلاصة


, , . , . .


, ^^

Source: https://habr.com/ru/post/ar430690/


All Articles