من الغريب أنه لم يتم ذكر حبري حتى الآن حول الاقتراح الصاخب لمعيار 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 ++ 20noexcept
- محدد الوظيفة ، الوظيفة لا ترمي استثناءات ديناميكية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
، والذي يمكنه:
- القضاء على نقص الذاكرة ومواصلة التنفيذ
- رمي استثناء
- برنامج التصادم
الآن 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, , , .
الخلاصة
, , . , . .
, ^^