This week you will finish the Buddhabrot rendering program. Part of this lab is code beautification, but most of it is focused on improving the usability of your program with standard C/C++ programming techniques.
The Concurrent Bounded Queue implementation from last week seems like a generally useful data structure. In fact, hopefully you were bothered a bit by writing the queue to only manage SP_MandelbrotPointInfo
elements! This week, turn the Concurrent Bounded Queue class into a class-template, parameterized on the element type. This will allow it to be used in other contexts besides just this program.
When you do this, you will only have a file cbqueue.h
; the cbqueue.cpp
file should go away. All function implementations will be defined inline, either in the class-template declaration, or afterward as inline functions.
Finally, put an include guard around the contents of cbqueue.h
, so that it can be included multiple times in a source file without error. (It is uncommon for a header file to be directly included multiple times, but it is very common for a header to be indirectly included multiple times via other header files. Using include guards keeps the C++ compiler from getting confused in these situations.)
The remainder of the lab is focused on improving the command-line interactions of your program.
Perhaps you have used programs with sophisticated command-line argument processing. For example, git
has very sophisticated command-line processing. It is very good to learn how to implement this kind of functionality, so that you can write sophisticated command-line tools that give users a lot of flexibility in how they are used.
The getopt()
and getopt_long()
functions are widely used to implement such argument processing, and it's a very good idea to learn how to use them. You can read about these functions here or here. (Alternately, you can type man 3 getopt
or man getopt_long
to learn more about these functions in your command terminal.) The best part of these documents is that they also include example code, which you can copy into your program's main()
function and then edit it as you see fit.
Update your main()
function to parse the following arguments with getopt_long()
:
-s|--size <img_size>
- image size in pixels. This should default to 800.
-p|--points <num_points>
- total number of random starting points to generate. This should default to 1,000,000.
-i|--iters <max_iters>
- maximum iteration limit for testing a point for membership in the Mandelbrot set. This should default to 1,000.
-t|--threads <num_threads>
- the number of producer threads to run. This should default to the value returned by the C++ std::thread::hardware_concurrency()
function, or 1 if this function returns 0.
Note that when you change your program to use getopt_long()
, you will no longer require that the user specifies four arguments, because there are reasonable defaults for all arguments.
Create a usage()
function that prints out the program usage to stderr. The usage should summarize the details given in the previous section. If you have seen other program usage messages, you have probably noticed that they all follow a similar pattern:
usage : program [options] filename ...
Brief description of the program's main purpose. It takes a
filename and maybe some other details.
-a1|--arg1 has some effects.
-a2|--arg2 <value> has some other effects.
In general:
Square brackets "[arg]
" indicate optional arguments or values.
Angle brackets "<value>
" tend to be used around argument values.
Different ways of saying the same thing can be separated with a "|
" pipe character. For example: "-t|--threads <num_threads>
"
Multiple positional parameters can be indicated with text like "file1 file2 ...
". Note: If you don't accept a varying number of arguments, don't use "...
"!
Note: If you want to be particularly clever, your usage()
function can take argv[0]
as an argument, so that it can print out the name of the program as it was specified on the command-line. This way you don't need to hard-code your program's name into your program.
Make sure your program outputs the usage information if any issues are encountered with the command-line arguments, and/or if the user passes -?
or -h
to your program. If your program outputs usage information, it should then terminate, rather than going on to do any computations.
Until now, you are likely using a function like atoi()
to parse integer arguments. (This is what we suggested in part 1.) In general, this is not recommended because atoi()
has no way of indicating that parsing failed. A much better approach is to use strtol()
, which indicates what part of the input string was successfully parsed by the function. In our case, we expect the entire string to be parsed, so we can write code like this:
// str is a C-style string, which is NUL (zero) terminated.
char *str = ... ; // Some string to parse into an int
char *str_end;
// str_end will be updated to point to the location in the string where
// the parsing function stops parsing. If str_end ends up pointing to
// the NUL character, the entire string was parsed.
int val = strtol(str, &str_end, /* base */ 10);
if (*psz_end != 0) {
... // Complain that the string didn't represent an integer!
}
Of course, there are other details to check as well, such as whether the number falls into a reasonable range, etc.
Update your code to use strtol()
to detect parsing issues with command-line arguments. If any errors are encountered, show your program's usage and then exit.
Since you have up to four arguments to parse, you should probably create a helper function that parses and verifies the integer arguments, to minimize code duplication. If you want to be particularly clever, you can also have this function verify that the integer falls into an acceptable range, and reports the issue to the user if it doesn't.
Finally, update your program to print out its configuration to standard error, before it continues onto its computations. This way, the user will get positive feedback that the program is using the configuration that the user specified.
To reiterate, the configuration output should include:
Once you have completed all of the above steps, make sure that your program continues to work as intended, and that you haven't accidentally mangled the output of your image data.
Once you have finished testing and debugging your program, submit these files to codePost.io:
cbqueue.h
(cbqueue.cpp
should no longer be necessary!)mbrot.h
and mbrot.cpp
bbrot.h
and bbrot.cpp
Makefile
We will use a fresh copy of image.h
, so you don't need to submit this file.