Buddhabrot Renderer, Part 3

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.

Concurrent Bounded Queue Class-Template

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.)

Command-Line Interactions

The remainder of the lab is focused on improving the command-line interactions of your program.

Parsing Command-Line Arguments

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():

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.

Program Usage Information

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:

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.

Verifying Numeric Arguments

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.

Program Configuration

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:

Testing

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.

All Done!

Once you have finished testing and debugging your program, submit these files to codePost.io:

We will use a fresh copy of image.h, so you don't need to submit this file.