ایمنی در استثنا

**ایمنی در برابر استثنا** به حالتی گفته می‌شود که در آن کد در صورت بروز استثنا همچنان به‌درستی عمل می‌کند. برای کمک به تضمین این نوع ایمنی، توسعه‌دهندگان کتابخانه‌ی استاندارد ++C مجموعه‌ای از سطوح ایمنی در برابر استثنا را تعریف کرده‌اند؛ این سطوح در واقع تضمین‌هایی قراردادی هستند که رفتار عملیات یک ساختار داده را در مواجهه با استثنا مشخص می‌کنند. این تضمین‌ها به توسعه‌دهندگان کتابخانه‌ها و کاربران آن‌ها کمک می‌کند تا در تحلیل درستی مدیریت استثناها، بهتر عمل کنند. این سطوح ایمنی نه‌تنها در زبان ++C بلکه در سایر زبان‌ها و سازوکارهای مدیریت خطا نیز قابل اعمال هستند.

## تاریخچه
همان‌طور که *[[:en:David_Abrahams_(computer_programmer)|دیوید آبراهامز]]* می‌نویسد، «پیش از آن‌که زبان ++C استثناها را معرفی کند، هیچ‌کس از 'ایمنی در برابر خطا' سخنی به میان نیاورده بود.» این اصطلاح نخستین‌بار در سال ۱۹۹۴ در قالب موضوعی در اسناد گروه استانداردسازی زبان ++C (JTC1/SC22/WG21) ظاهر شد. ایمنی در برابر استثنا برای کتابخانه‌ی استاندارد ++C نخستین‌بار توسط آبراهامز و در پیاده‌سازی STLport به‌صورت رسمی تعریف شد و تفاوت میان "ایمنی پایه" و "ایمنی قوی" را بنیان نهاد. این تقسیم‌بندی بعدها در قالب پیشنهادی دیگر گسترش یافت و تضمین‌های سه‌گانه‌ی «پایه»، «قوی» و «بدون پرتاب» (*nothrow*) شکل گرفتند.

## پیش زمینه
استثناها نوعی از جریان کنترل غیرمحلی (*non-local control flow*) را فراهم می‌کنند؛ به این معنا که یک استثنا می‌تواند از درون تابع فراخوانی‌شده «به بالا حباب بزند» و به کدی در سطح بالاتر منتقل شود. این روندِ «بالا رفتن» ممکن است با مختل‌کردن وضعیت‌های درونی یک ساختار داده‌ی قابل تغییر (mutable)، به بروز اشکال در ایمنی استثنا منجر شود. این فرآیند به‌صورت زیر رخ می‌دهد:

# در یکی از مراحل یک عملیات روی ساختار داده، تغییری اعمال می‌شود که یک وضعیت پایدار (invariant) را نقض می‌کند؛
# استثنایی پرتاب می‌شود و اجرای کد به‌جای ادامه، به سطح بالاتری منتقل می‌شود و در نتیجه، کدهای باقی‌مانده‌ای که باید وضعیت پایدار را بازگردانند اجرا نمی‌شوند؛
# استثنا یا گرفته و مدیریت می‌شود، یا اجرای کد وارد یک بلوک *finally* می‌شود؛
# ساختار داده‌ای که وضعیت آن اکنون ناپایدار است، توسط کدی که انتظار دارد وضعیت پایدار باشد، مورد استفاده قرار می‌گیرد، و در نتیجه، اشکال (باگ) رخ می‌دهد.

کدی که چنین رفتاری از خود نشان می‌دهد، به عنوان «فاقد ایمنی در برابر استثنا» شناخته می‌شود.

## دسته‌بندی
کتابخانه‌ی استاندارد ++C چند سطح از ایمنی در برابر استثنا را (به‌ترتیب نزولی از نظر ایمنی) تعریف می‌کند:

* **تضمین عدم پرتاب (No-throw guarantee)** یا **شفافیت در برابر خطا (failure transparency)**:   در این سطح، عملیات تضمین می‌شوند که حتی در شرایط استثنایی نیز با موفقیت انجام شده و همه‌ی پیش‌شرط‌ها را برآورده کنند. اگر استثنایی رخ دهد، به‌طور داخلی مدیریت شده و در سطح کاربر (client) مشاهده نمی‌شود.
* **ایمنی قوی در برابر استثنا (Strong exception safety)** که با نام‌های **تعهد اجرایی** یا **رفتار برگشتی (rollback semantics)** نیز شناخته می‌شود:   در این سطح، عملیات ممکن است با شکست مواجه شوند، اما در صورت شکست، تضمین می‌شود که هیچ اثری از آن عملیات باقی نمی‌ماند و مقادیر اولیه بدون تغییر حفظ می‌شوند.
* **ایمنی پایه (Basic exception safety)**:   در این سطح، ممکن است عملیات ناقص اجرا شوند و برخی اثرات جانبی به‌جا بمانند، اما تمام وضعیت‌های پایدار حفظ می‌شوند. به بیان دیگر، داده‌های ذخیره‌شده معتبر خواهند بود، هرچند ممکن است با مقادیر اولیه تفاوت داشته باشند. نشت منابع (از جمله نشت حافظه) معمولاً با پایدار ماندن یک وضعیت کلی کنترل می‌شود که بیان می‌کند همه‌ی منابع به‌درستی مدیریت شده‌اند.
* **فاقد ایمنی در برابر استثنا (No exception safety)**:   در این حالت، هیچ‌گونه تضمینی در مورد رفتار کد در زمان پرتاب استثنا ارائه نمی‌شود.

معمولاً برای نوشتن کد مقاوم، دست‌کم ایمنی پایه توصیه می‌شود. دستیابی به سطوح بالاتر از ایمنی ممکن است دشوار بوده و به‌دلیل نیاز به کپی‌برداری‌های اضافی، هزینه‌ی عملکردی به‌همراه داشته باشد. یکی از سازوکارهای کلیدی در تأمین ایمنی استثنا، استفاده از بلوک *finally* (یا سازوکاری مشابه) است که تضمین می‌کند بخشی از کد همواره، حتی در صورت وقوع استثنا، اجرا خواهد شد. بسیاری از زبان‌های برنامه‌نویسی ساختارهایی برای تسهیل این موضوع دارند، مانند الگوی ''dispose'' که در قالب‌هایی چون `using`، `with` یا `try-with-resources` به‌کار گرفته می‌شود.

## مثال
در نظر بگیرید نوعی بردار هوشمند (*smart vector*) مانند `std::vector` در ++C یا `ArrayList` در جاوا. زمانی که عنصری مانند `x` به برداری به نام `v` افزوده می‌شود، بردار باید واقعاً `x` را به فهرست داخلی خود اضافه کند و همچنین شمارنده‌ای را به‌روزرسانی کند که نشان می‌دهد چند عنصر در `v` وجود دارد. اگر ظرفیت موجود کافی نباشد، ممکن است نیاز به تخصیص حافظه‌ی جدید نیز باشد.

سطوح مختلف ایمنی در برابر استثنا در این مثال به‌صورت زیر پیاده‌سازی می‌شوند:

* **تضمین عدم پرتاب (No-throw guarantee):**   این سطح با اطمینان از این‌که تخصیص حافظه هرگز با شکست مواجه نمی‌شود، یا با تعریف رفتاری مشخص در صورت شکست تخصیص (مثلاً بازگرداندن یک مقدار بولی که نشان می‌دهد آیا درج موفق بوده یا نه) قابل پیاده‌سازی است.
* **ایمنی قوی (Strong exception safety):**   در این حالت، ابتدا همه‌ی تخصیص‌های لازم انجام می‌شوند و تنها در صورت موفقیت، داده‌ها با بافر جدید تعویض می‌شوند (). در نتیجه، یا `x` با موفقیت به `v` افزوده می‌شود، یا اگر مشکلی رخ دهد، `v` بدون تغییر باقی می‌ماند.
* **ایمنی پایه (Basic exception safety):**   در این سطح، تضمین می‌شود که شمارنده‌ی عناصر، اندازه‌ی نهایی `v` را به‌درستی بازتاب دهد. برای مثال، اگر خطایی رخ دهد، تابع درج ممکن است کل محتوای `v` را آزاد کرده و شمارنده را به صفر بازنشانی کند. در صورت شکست عملیات، منابع از دست نمی‌روند، اما مقدار پیشین `v` نیز حفظ نمی‌شود.
* **فاقد ایمنی در برابر استثنا (No exception safety):**   در این حالت، شکست در عملیات درج ممکن است منجر به محتوای خراب در `v`، مقدار نادرست در شمارنده یا نشت منابع شود.

## منابع


## پیوند به بیرون

* Herb Sutter: Exceptional C++: 47 Engineering Puzzles, Programming Problems, and Solutions, 2000
* Jon Kalb: Exception-Safe Coding in C++, with C++Now! 2012 presentations on exception safety.
* Related discussion on Stackoverflow: C++: do you (really) write exception safe code
رده:سی++
رده:کنترل جریان
رده:موضوعات مربوط به زبان‌های برنامه‌نویسی