The pattern that is most predominant in programming is:
Read how you can create an elegant piece of code, while reducing the maintenance load.
The resource must always be freed, and cannot be used after
It therefore has a limited scope.
It is very important to release the file handle – failure to do so, repeatedly, may cause your file system to block.
Here’s an example of how to draw a single line:
Let’s assume that as you add items to the listview, you notice an invalid item, and you have to ask whether the user whether to continue adding items, or not.
- Setup some state
- Do some work
- Clean up
Read how you can create an elegant piece of code, while reducing the maintenance load.
Example: Reading a file
This is the second most common example, afterHello World!
.FILE *fp = fopen("output.txt", "wt"); fprintf(fp, "Hello World"); fclose(fp);The file handle,
fp
, is never used unless it has been initialized with fopen()
. The resource must always be freed, and cannot be used after
fclose()
. It therefore has a limited scope.
It is very important to release the file handle – failure to do so, repeatedly, may cause your file system to block.
Example: OpenGL
In OpenGL you cannot put any vertices through the pipeline, unless you have declared what they’ll be used for, usingglBegin()
. No shape is going to be drawn unless you conclude that the vertices are over, using glEnd()
.Here’s an example of how to draw a single line:
glBegin(GL_LINES); // initialize glVertex3f(0.0, 0.0, 0.0); // work glVertex3f(15, 0, 0); glEnd(); // finalizeIf, additionally, you wish to temporarily set the line thickness, it is wise that you declare it before start drawing, and set it back to the previous thickness, after you’re done.
GLfloat prev_line_width; glGetFloatv(GL_LINE_WIDTH, &prev_line_width); glLineWidth(15); // change width glBegin(GL_LINES); // initialize glVertex3f(0.0, 0.0, 0.0); // work glVertex3f(15, 0, 0); glEnd(); // finalize glLineWidth(prev_line_width); // resume widthNote that the order of actions should be reversed after the work ahs taken place.
Example: Filling a listview
Filling a listview can be a time-consuming task. You decide to temporarily set the mouse cursor to busy while filling takes place. You also want to turn off the sorting of the list, because sorting while you append in the list will delay the process.int prev_mouse_cursor, prev_sort_state; prev_mouse_cursor = get_cursor(); set_cursor(BUSY); prev_sort_state = get_sorting(list); set_sorting(list, NO_SORT); ... item = add_item_to_list(list, text); // work ... set_sorting(list, prev_sort_state); set_cursor(prev_mouse_cursor);And notice how hairy things can get when the situation is just slightly more complicated:
Let’s assume that as you add items to the listview, you notice an invalid item, and you have to ask whether the user whether to continue adding items, or not.
int prev_mouse_cursor, prev_sort_state; prev_mouse_cursor = get_cursor(); set_cursor(BUSY); prev_sort_state = get_sorting(list); set_sorting(list, NO_SORT); ... item = add_item_to_list(list, text); // work if (is_invalid(item)) { int prev_mouse_cursor_2 = get_cursor(); set_cursor(ARROW); if (ask("bad item - should I continue?") == NO) { set_sorting(list, prev_sort_state); set_cursor(prev_mouse_cursor); return; } set_cursor(prev_mouse_cursor_2); } ... set_sorting(list, prev_sort_state); set_cursor(prev_mouse_cursor);
Solution
In C++, the initialize/finalize concept is built into the language. The concept is called constructor and destructor. The constructor is called when an object is created, and the destructor is called when the object leaves out of scope. If many objects are created in the stack, the destruction order is the reverse of the order that they were constructed.Example revisited: filling the listview
ScopedCursor temporarily_set_cursor_to(BUSY); ScopedSorting temporarily_set_sorting_state_to(list, NO_SORT); ... item = add_item_to_list(list, text); // work if (is_invalid(item)) { ScopedCursor temporarily_set_cursor_to(ARROW); if (ask("bad item - should I continue?") == NO) { return; } }How simpler is that to read and maintain ?
- The
temporarily_set_cursor_to
andtemporarily_set_sorting_state_to
are merely variables, that will die after their scope ends, thereby rolling back their actions. The memory footprint is exactly the same as the one with the C implementation. Running time is exactly the same, and so is the execution order. - I’ve carefully named the variables so that they read like english.
- the struct’s name,
Scoped
is chosen so that it reminds me of its lifetime
struct
, that runs a function on construction, and another function when it goes out of scope:struct ScopedCursor { int prev_cursor; ScopedCursor(int new_cursor) { // on construction prev_cursor = get_cursor(); set_cursor(new_cursor); } ~ScopedCursor() { // on destruction set_cursor(prev_cursor); } };
Written with StackEdit.
No comments:
Post a Comment