من الغريب أنه لم يتم ذكر حبري حتى الآن حول الاقتراح الصاخب لمعيار 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;  
إذا كان سبب الخطأ غير مثير للاهتمام بالنسبة لنا ، أو يمكن أن يتكون الخطأ فقط في "غياب" النتيجة ، فيمكن استخدام 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 من الكائن القديم إلى الكائن الجديد. لم يتم حذف الكائن القديم ، يسميه المؤلفون "إسقاطه على الأرض".
النوع قابل للنقل بشكل تافه من وجهة نظر المترجم إذا تحقق أحد الشروط (العودية) التالية:
- إنه قابل للحركة بشكل تافه + قابل للتلف بشكل تافه (مثل بنية intأو POD)
- هذه هي الفئة المميزة [[trivially_relocatable]]
- هذه فئة جميع أعضائها يمكن نقلهم بسهولة.
يمكنك استخدام هذه المعلومات مع std::uninitialized_relocate ، الذي ينفذ نقل init + حذف بالطريقة المعتادة ، أو يتم تسريعه إن أمكن. يُقترح وضع علامة على [[trivially_relocatable]] معظم أنواع المكتبة القياسية ، بما في ذلك std::string و std::vector و std::unique_ptr . النفقات العامة std::vector<std::unique_ptr<T>> مع وضع هذا في الاعتبار ، سيختفي الاقتراح.
ما هو الخطأ في الاستثناءات الآن؟
تم تطوير آلية استثناء C ++ في عام 1992. تم اقتراح خيارات تنفيذ مختلفة. من بين هذه ، تم اختيار آلية جدول الاستثناءات التي تضمن عدم وجود عبء إضافي للمسار الرئيسي لتنفيذ البرنامج. لأنه منذ لحظة إنشائها ، كان من المفترض أن يتم طرح الاستثناءات في حالات نادرة جدًا .
مساوئ الاستثناءات الديناميكية (أي العادية):
- في حالة الاستثناء الذي تم طرحه ، يبلغ الحمل في المتوسط حوالي 10000-100.000 دورة وحدة المعالجة المركزية ، وفي أسوأ الحالات ، يمكن أن تصل إلى ترتيب المللي ثانية
- زيادة حجم الملف الثنائي بنسبة 15-38٪
- عدم التوافق مع واجهة برمجة C
- استثناء ضمني يرمي الدعم في جميع الوظائف باستثناء noexcept. يمكن طرح استثناء في أي مكان تقريبًا في البرنامج ، حتى عندما لا يتوقع مؤلف الوظيفة ذلك
بسبب هذه العيوب ، فإن نطاق الاستثناءات محدود بشكل كبير. عندما لا يمكن تطبيق الاستثناءات:
- عندما تكون الحتمية مهمة ، أي حيث يكون من غير المقبول أن يعمل الرمز "أحيانًا" 10 ، 100 ، 1000 مرة أبطأ من المعتاد
- عندما لا تكون مدعومة في ABI ، على سبيل المثال ، في المتحكمات الدقيقة
- عندما تتم كتابة جزء كبير من التعليمات البرمجية في C
- في الشركات التي لديها عدد كبير من الرموز القديمة ( Google Style Guide ، Qt ). إذا كانت هناك وظيفة واحدة على الأقل غير آمنة للاستثناء في الشفرة ، فعندئذٍ وفقًا لقانون اللغط ، سيتم طرح استثناء من خلاله عاجلاً أو آجلاً وإنشاء خطأ
- في الشركات التي توظف المبرمجين الذين ليس لديهم فكرة عن سلامة الاستثناء
وفقًا للاستطلاعات ، في أماكن العمل الخاصة بمطوري 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 :
- status_code- نوع نموذج يمكن استخدامه لتخزين أي رموز خطأ يمكن تصورها تقريبًا (جنبًا إلى جنب مع مؤشر إلى- status_code_category) ، دون استخدام الاستثناءات الثابتة
- يجب أن يكون Tقابلاً للانتقال والنسخ بشكل تافه (الأخير ، IMHO ، لا يجب أن يكون إلزامياً). عند النسخ والحذف ، يتم استدعاء الوظائف الافتراضية منstatus_code_category
- لا يمكن تخزين status_codeفقط بيانات الخطأ ، ولكن أيضًا معلومات إضافية حول عملية مكتملة بنجاح
- لا تقوم الدالة 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 . عمليًا ، سيكون هذا أحد الخيارات التالية:
- الأعداد الصحيحة (int)
- std::exception_handle، أي مؤشر إلى استثناء ديناميكي تم طرحه
- 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 {  
الاستثناءات في 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::variantstd::optional،std::expected،std::variant. أمثلة:stoi()؛vector::find()؛map::insert.
في المكتبة القياسية ، من الأكثر موثوقية التخلي تمامًا عن استخدام الاستثناءات الديناميكية من أجل جعل التجميع "بدون استثناءات" قانونيًا.
تخطئ
يجب استبدال الوظائف التي تستخدم errno للعمل بسرعة وسهولة مع رموز الخطأ C و C ++ fails(int) throws(std::errc) ، على التوالي. لبعض الوقت ، ستتعايش الإصدارات القديمة والجديدة من وظائف المكتبة القياسية ، ثم سيتم الإعلان عن القديم القديم.
نفاد الذاكرة
يتم التعامل مع أخطاء تخصيص الذاكرة عن طريق الخطاف العام new_handler ، والذي يمكنه:
- القضاء على نقص الذاكرة ومواصلة التنفيذ
- رمي استثناء
- برنامج التصادم
الآن 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 وثائق:
- P709 - المستند الأصلي من شعار سوتر
- P1095 - استثناءات محددة في رؤية نيال دوغلاس ، تغيرت بعض اللحظات ، إضافة توافق لغة سي
- P1028 - API من تطبيق الاختبار std::error
لا يوجد حاليا مترجم يدعم الاستثناءات الثابتة. وبناء على ذلك ، ليس من الممكن بعد وضع معاييرهم.
C++23. , , , C++26, , , .
الخلاصة
, , . , . .
, ^^