2.7 - חריגות וערבויות פעולה - פתרון
פתרון - בונים בנפרד ומחליפים בסוף¶
הפתרון הבא שומר את המקור ללא שינוי עד ה־commit:
#include <cassert>
#include <cstddef>
#include <span>
#include <stdexcept>
#include <vector>
void replace_values(std::vector<int>& destination,
std::span<const int> input,
std::size_t fail_after = static_cast<std::size_t>(-1)) {
std::vector<int> candidate;
for (int value : input) {
if (value < 0 || value > 100) {
throw std::invalid_argument("value out of range");
}
if (candidate.size() == fail_after) {
throw std::runtime_error("injected");
}
candidate.push_back(value);
}
destination.swap(candidate);
}
int main() {
std::vector<int> original{7, 8};
const int invalid[]{1, 2, 101};
try {
replace_values(original, invalid);
assert(false);
} catch (const std::invalid_argument&) {
assert((original == std::vector<int>{7, 8}));
}
const int valid[]{1, 2, 3};
try {
replace_values(original, valid, 1);
assert(false);
} catch (const std::runtime_error&) {
assert((original == std::vector<int>{7, 8}));
}
replace_values(original, valid);
assert((original == std::vector<int>{1, 2, 3}));
}
ה־candidate משתמשת ב־allocator הרגיל, ולכן החלפת שתי המכולות מתאימה לחוזה שלהן. כשל ב־push_back משנה לכל היותר את המועמד, שמנוקה בזמן unwinding. המקור עדיין אינו מעורב.
יש כאן גם יתרון כאשר input מתייחס לאיברים של destination: אנחנו קוראים את הטווח לפני שמחליפים את האחסון. גרסה שמנקה את המקור בתחילת הפונקציה הייתה עלולה לפסול את הקלט שלה עצמה.
ערבות בסיסית¶
הגרסה החלקית יכולה לבצע בדיקה ואז push_back ישירות ל־destination. אם הערך השלישי שגוי, שני האיברים הראשונים כבר נוספו, אבל המכולה תקינה ואינה מדליפה.
זאת אינה הפרה אם כך הוגדר החוזה. הבעיה היא להציג את הפונקציה כפעולה אטומית בזמן שהלקוח עלול לראות תוצאה חלקית אחרי שגיאה.
העברת שגיאה¶
הביטוי throw ללא ארגומנט בתוך catch משגר מחדש את החריגה הפעילה. תפיסה לפי reference אינה יוצרת עותק של תת־אובייקט בסיס, ולכן אינה מבצעת slicing של המידע שנזרק.
הסימון noexcept אינו משנה את אופן פעולת allocator. אם bad_alloc יוצאת מפונקציה שהבטיחה noexcept, התהליך עלול להסתיים במקום להגיע ל־catch שהלקוח ציפה לו. לכן ההצהרה היא התחייבות שצריך להצדיק, לא דרך לטפל בכשל.