הייתי שמח מאוד שיהיו לי עוד יכולות לתאר באמצעות c++ atributes עוד מרכיבים של ההגדרה ביחד עם בדיקה טובה יותר והטמעה עוד יותר טוב בכלל הקומפיילרים. ישנם גם דברים שחסרים לי ב GSL לעומת SAL, לדוגמה - pre/post conditions, וגם העובדה שאני לא מצאתי דרך שניתן לאפיין סוגי מצביעים בקלות כמו שיש ב SAL ושהבודק הסטטיסטי של VS ישתמש בו כהלכה.
מאוד בגדול ב SAL אנחנו מוסיפים אנוטציות, מעין הערות, זה מחייב ללמוד משהוא דמויי שפה שלמה בשביל לכתוב ב C++, יש דברים הכל מבדקית טיפוסי חזרה, דרך תיאור שמאפשר לבודק הסטטי למצוא בעיות לפי התיאור שכתבנו מסביב לערכי החזרה של הפונקציה והמאפיינים של הפרמטרים של הפונקציה. למעשה בכל פרוייקט גדול המיועד לסביבה וינדוס שאני מכיר יש תמיד שימוש בSAL או מוצר מתחרה שלהם, הגישה הזאת דורשת עבודה, אבל היא שווה את זה.
הנה דוגמא לפונקציה עם SAL:
//Peform conditional library function ,based on system avilibility, error flows are expressed via return type. //success flow are either library_result_t::OK or library_result_t::valid_for_only_10_minutes; //invalid cases would may be caused of a non initlized system, invalid frequency or incorrect voltage _Success_(return == library_result_t::OK || return == library_result_t::valid_for_only_10_minute) library_result_t library_conditional_functionality(type_t & r_resultValue);
הפונקציה שלפנינו, מכילה אנוטציה המציינת מתי הפונקציה שלנו החזירה ערך הצלחה לביצוע העבודה, ויש לה פרמטר בו היא מכניסה את הערך או מהות העבודה. כאשר אנחנו משתמשים בגישה הזאת יש לנו שני ערכים מנותקים שאיתם אנו צריכים לעבוד, לשיטה הזאת יש ייתרונות וחסרונות, לפעמים יש עדיפות ששני המושגים האלה יהיו ביחד.
בפוסט הזה אני מציג איך std::expected שמאפשר לנו לאחד את ערך החזרה (נכונות ביצוע העבודה) מהפונקציה, ביחד עם פרמטר המגדיר את הערך שייצרנו, שזה רכיב מאוד קטן אבל נדרש בגישות עבודה. למשל בעבר או שהיינו משתמשים ב enum ביחד עם ערך או בצורה מופשטת כאשר טיפוס החזרה מהפונקציה היא נכונות הפונקציה, ואחד הפרמטרים היו ערך ההחזרה או שימוש במנגונים כמו std::optional או אפילו מעטפות של ערך החזרה עם נכונות החזרה כערך החוזר מהפונקציה.
לשם הדוגמה, נניח ויש לנו מבנה שאורז קופסאות, ערך החזרה הוא המצב שמגדיר שאנחנו הצלחצנו לארוז למשל, או נכשלנו לארוז, בעוד הפרמטר שמגדיר את הערך , זו הקופסא שיוצאת מהמבנה.
למשל עם optional :
library_result_t library_conditional_functionality(type_t & r_resultValue); std::optional<std::string> single_success_flow() { type_t library_result_value{}; const library_result_t result = library_conditional_functionality(library_result); if (conditional_function(result)) { return convert_to_string(library_result_value); } return {}; }או לחילופן גישה המשתמשת בערכי חזרה אבל במצב הצלחה מעכנת פרמטר לפונקציה
//Peform conditional library function ,based on system avilibility, error flows are expressed via return type. //success flow are either library_result_t::OK or library_result_t::valid_for_only_10_minutes; //invalid cases would may be caused of a non initlized system, invalid frequency or incorrect voltage library_result_t library_conditional_functionality(type_t & r_resultValue);כאשר יש שימוש ב SAL אנחנו נזהה את זה בצורה הבאה:
//Peform conditional library function ,based on system avilibility, error flows are expressed via return type. //success flow are either library_result_t::OK or library_result_t::valid_for_only_10_minutes; //invalid cases would may be caused of a non initlized system, invalid frequency or incorrect voltage _Success_(return == library_result_t::OK || return == library_result_t::valid_for_only_10_minute) library_result_t library_conditional_functionality(type_t & r_resultValue);כאשר מגיע אליינו ה std::expected הוא מאפשר לנו תחליף מאוד נוח ל std::optional
//Code adjusted by Boris Shtrasman to express std::expected idiom to catch errors, this is for training purposes only //This code intentionally does not use proper exception handeling, SAL or GSL //This code exist to show the proper use of return type logic with use of std::expected in contrast to exception flows and SAL. #include <cmath> #include <expected> #include <iomanip> #include <iostream> #include <string_view> enum class extracting_double_from_prefix_error_types { value_too_large_to_store_in_type, invalid_input, overflow }; enum class result_validity_t { OK = 0, ERROR =1, LIBRARY_ERROR = 2, INVALID_COMPILER_SETTINGS = 3, }; std::expected<double, extracting_double_from_prefix_error_types> extract_longest_double_from_prefix(std::string_view& str) { if (str.empty()) { return std::unexpected(extracting_double_from_prefix_error_types::invalid_input); } const char* begin = str.data(); char* end_ptr_would_hold_error; double retval = std::strtod(begin, &end_ptr_would_hold_error);//This may or may not set errno in case of error! if (begin == end_ptr_would_hold_error) { return std::unexpected(extracting_double_from_prefix_error_types::invalid_input); } if (std::isinf(retval)) { return std::unexpected(extracting_double_from_prefix_error_types::overflow); } //always remember to have your constannts on your left side, that way you have less chances to do a stupid bug of asigment instead of comparison if (HUGE_VAL == retval) { return std::unexpected(extracting_double_from_prefix_error_types::value_too_large_to_store_in_type); } return retval; } //return OK in case of success, and print the value to the screen //return a non OK value in case of any failure //intentiional no exception catching, nor SEH catching result_validity_t treat_single_conversion(std::ostream& r_debugstream,std::string_view str) { if (str.empty()) { return result_validity_t::ERROR; } //this extract a double value from the prefix const auto num = extract_longest_double_from_prefix(str); if (num.has_value()) { r_debugstream << __FILE__ << " " << __LINE__ <<": valid case str <" << str << "> value: " << *num << "\n"; return result_validity_t::OK; } switch (num.error()) { case extracting_double_from_prefix_error_types::invalid_input: r_debugstream << __FILE__ << " " << __LINE__ << ": invalid input <" << str << ">\n"; return result_validity_t::ERROR; case extracting_double_from_prefix_error_types::overflow: r_debugstream << __FILE__ << " " << __LINE__ << ": overflow <" << str << ">\n"; return result_validity_t::ERROR; case extracting_double_from_prefix_error_types::value_too_large_to_store_in_type: r_debugstream << __FILE__ << " " << __LINE__ << ": internal error function can not handle value <" << str << ">\n"; return result_validity_t::LIBRARY_ERROR; //If you can setup your compiler properly avoid using default, you need to make sure it will fail compliation if there are unhandled enum values, use your VS settings to fail compilation in such cases } //Why this unhandled cases exist here ? well it is here to be able to identify incorrectly configured compiler, I had not set a static assert or an exception here , as I need it to be working on most compiler with not too many problems return result_validity_t::INVALID_COMPILER_SETTINGS;//This should never happen , but exist to avoid unreachable. } int main() { std::ostringstream debugstream; bool failed_to_treat_a_single_value = false;
//Just iterate on a collection of hard coded values for the example, normally we would be using the test case idiom and returning ok/nok flow for each one of them here for (auto value : {"42","inf","0x10", "0xFFFFFFFFFFFFFFF","NaN","bla bla"}) { result_validity_t test_case_result = treat_single_conversion(debugstream,value); //in reality we need to check and handle each return type correctly, but that is just an example switch (test_case_result) { case result_validity_t::OK :break; case result_validity_t::INVALID_COMPILER_SETTINGS: std::cout << "compiler configuration error found\n"; [[fallthrough]]; case result_validity_t::ERROR:[[fallthrough]]; case result_validity_t::LIBRARY_ERROR: failed_to_treat_a_single_value = true; break; } } if (failed_to_treat_a_single_value) { std::cout << "log is enabled as we had found an error:\n" << debugstream.str() << "\n"; return 1; } return 0; }
זו היא דוגמית קוד המראה את היכולת והפשטות שבמשימוש בstd::expected.
עריכה: בדיעבד לא הסברתי טוב , ה GSL עצמו הוא לא בודק סטטיסטי צריך להפעיל את הבודק הסטטיסטי שיוכל לעבוד עם הקוד איתו עובדים, אני האמת תודות לאדם מאוד טוב לב , יש לי אוסף חוקים בזמן הבדיקה הסטטיסטית שתתריע הכל ממשתנה לא משומש, דרך בעיות המרת טיפוסים לא מפורשת , מצבים ב switch שלא מטופלים ועוד. לצערי קובץ החוקים הזה הוא אינו חופשי לכן אני לא יכול לשתף אותו. וכן לא הראתי פה שימוש ב GSL ו SAL כי הרעיון שלי היה להעביר את std::expected כאבן בניין