8.7.1. pyramid.cpp, Version 2

bulletproof code, pyramid, symbolic constant, getline (C-string), getline (string), atoi, getline, isdigit, argc, argv, strcpy, command line, in bounds, bounds check, stoi, regular expressions, getline, getline, string assignment Time: 00:04:23 | Download: Large, LargeCC, Small | Streaming, Streaming (CC) | Slides: PDF, PPTX

This example revisits the Chapter 3 pyramid.cpp program to demonstrate bulletproof data input while reviewing command line input. Users enter command-line data as C-string arguments, regardless of how the program uses them. Therefore, in this example, the program must convert a C-string command line argument to an integer.

#define HEIGHT 20				// (a)

#include <iostream>
using namespace std;

int main()
{
    for (int level = 0; level < HEIGHT; level++)
    {
        // draw the pyramid
    }

    return 0;
}
#include <iostream>
using namespace std;

int main(int argc, char* argv[])		// (b)
{
    int height = atoi(argv[1]);			// (c)

    for (int level = 0; level < height; level++)
    {
        // draw the pyramid
    }

    return 0;
}
pyramid.cpp with command line input. The initial pyramid example solved the problem and developed an algorithm that draws a pyramid based on its specified height. The version developed here only changes how a user specifies the pyramid's height, leaving the drawing code unchanged and omitted for brevity.
  1. The original program specified the pyramid's height with a symbolic constant.
  2. The operating system passes all command line input into a program through two arguments added to function main
    • argc is a count (i.e., the number) of command line elements, including the program name.
    • argv is an array or vector containing all the command line elements as C-strings, making the program responsible for converting them from C-strings to an appropriate type when needed.
  3. The atoi (or ASCII to integer, where ASCII is a synonym for "text") function converts the C-string to an integer. Unlike the itoa function used in cpalnumber.cpp, atoi is a standard, universally available function. See C-string to number for a list of conversion functions.

This input method works if the user doesn't make an input error, but it isn't "bulletproof." Making it bulletproof requires testing for two potential errors. First, the program must verify that the user has entered input on the command line. Second, if there is input, the program must verify that it is in the correct format. The program will fail catastrophically if the user makes either input error.

Verify Command Line InputVerify Correct Input Format
int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        cerr << "USAGE: pyramid <height>" << endl;
        exit(1);
    }

    int height = atoi(argv[1]);
        . . .
}
int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        cerr << "USAGE: pyramid <height>" << endl;
        exit(1);
    }

    for (size_t i = 0; i < strlen(argv[1]); i++)
        if (! isdigit(argv[1][i]))
        {
            cerr << "Invalid integer: " << argv[1] << endl;
            exit(2);
        }

    int height = atoi(argv[1]);
        . . .
}
(a)(b)
Bulletproof pyramid with command line input. This figure takes an incremental approach to developing a bulletproof version of the pyramid program.
  1. argc is the number of command line elements, including the program name. Consequently, a correct command line has two elements: pyramid 20.
  2. argv is a zero-indexed array storing the command line elements, the program name in the first position, and the pyramid's height in the second. The for-loop validates the characters in the height, ensuring that the program can convert them to an integer. The order of the tests is significant - if they are reversed, the program could attempt to access a non-existent array.
Converting the argument string to a number doesn't end the validation process. Programmers can further enhance the program's robustness by restricting the pyramid's height to a positive value that fits on a typical console widow:
if (height < 0 || height > 20)
{
    cerr << "Height is out of bounds" << endl;
    exit(3);
}
Although isdigit rejects a minus sign, the if-statement tests for a value less then zero to demonstrate a typical bounds check. Finally, programmers can throw an exception (introduced in the next chapter) in place of calling the exit function.

 

int main(int argc, char* argv[])
{
    char input[10];						// (a)

    if (argc != 2)
    {
        cout << "Please enter the pyramid height: ";
        cin.getline(input, 10);					// (b)
    }
    else
        strcpy(input, argv[1]);					// (c)
    
    for (size_t i = 0; i < strlen(input); i++)
        if (! isdigit(input[i]))
        {
            cerr << "Invalid integer: " << input << endl;
            exit(1);
        }

    int height = atoi(input);
        . . .
}
Flexible pyramid. The flexible version of the pyramid program allows the user to enter the pyramid's height on the command line or the console. The logic and structure of the for-loop validating the string characters remain unchanged, but requires adding a new variable, allowing a single loop to validate input from the command line and console.
  1. A C-string to hold the user input.
  2. Console input as a string.
  3. In the case of command line input, copying the command line argument to input allows the for-loop to validate the input characters regardless of their source.

 

int main(int argc, char* argv[])
{
    string input;						// (a)

    if (argc != 2)
    {
        cout << "Please enter the pyramid height: ";
        getline(cin, input);					// (b)
    }
    else
        input = argv[1];					// (c)
    
    for (size_t i = 0; i < input.length(); i++)			// (d)
        if (! isdigit(input[i]))
        {
            cerr << "Invalid integer: " << input << endl;
            exit(1);
        }

    int height = stoi(input);					// (e)
        . . .
}
string version of pyramid. Programs can't entirely eliminate C-strings when dealing with command line arguments, but they can switch to string objects relatively early. The logic and program structure remain unchanged.
  1. Change input to a string.
  2. Change getline to the string version.
  3. The string assignment operator converts the C-string to a string.
  4. Change to getting the length of a string.
  5. Change atoi to stoi.

 

#include <regex>
   . . .
if (argc != 2) { ... }

if (! regex_match(input, regex("[1-9]|1[0-9]|20")))
{
    cerr << "Invalid: " << input << endl;
    exit(1);
}

int height = stoi(input);
    . . . 
Regular expression version of pyramid. For comparison and a preview of a more sophisticated bulletproofing technique, the final example presents (with little explanation) a solution using a regular expression. The regex_match and regex functions, and the regular expression, highlighted in red, replace the for-loop and the bounds checks. The vertical bar, |, is the "or" operator, breaking the expression into three sub-expressions: Adding spaces to the expression would make it easier to read. However, they would become part of the expression, so they are inappropriate here. Intro To Regular Expressions goes into much more detail.

Downloadable Bulletproof Pyramid Examples

ViewDownloadComments
pyramid2.cpp pyramid2.cpp All versions of the bulletproof pyramid code
pyramid_re.cpp pyramid_re.cpp A regular expression version of pyramid