Book Image

Modern C++: Efficient and Scalable Application Development

By : Richard Grimes, Marius Bancila
Book Image

Modern C++: Efficient and Scalable Application Development

By: Richard Grimes, Marius Bancila

Overview of this book

C++ is one of the most widely used programming languages. It is fast, flexible, and used to solve many programming problems. This Learning Path gives you an in-depth and hands-on experience of working with C++, using the latest recipes and understanding most recent developments. You will explore C++ programming constructs by learning about language structures, functions, and classes, which will help you identify the execution flow through code. You will also understand the importance of the C++ standard library as well as memory allocation for writing better and faster programs. Modern C++: Efficient and Scalable Application Development deals with the challenges faced with advanced C++ programming. You will work through advanced topics such as multithreading, networking, concurrency, lambda expressions, and many more recipes. By the end of this Learning Path, you will have all the skills to become a master C++ programmer. This Learning Path includes content from the following Packt products: • Beginning C++ Programming by Richard Grimes • Modern C++ Programming Cookbook by Marius Bancila • The Modern C++ Challenge by Marius Bancila
Table of Contents (24 chapters)
Title Page
Copyright
About Packt
Contributors
Preface
12
Math Problems
13
Language Features
14
Strings and Regular Expressions
15
Streams and Filesystems
16
Date and Time
17
Algorithms and Data Structures
Index

Controlling Execution Flow


C++ provides many ways to test values and loop through code.

Using Conditional Statements

The most frequently used conditional statement is if. In its simplest form, the if statement takes a logical expression in a pair of parentheses and is immediately followed by the statement that is executed if the condition is true:

    int i; 
    std::cin >> i; 
    if (i > 10) std::cout << "much too high!" << std::endl;

You can also use the else statement to catch occasions when the condition is false:

    int i; 
    std::cin >> i; 
    if (i > 10) std::cout << "much too high!" << std::endl; 
    else        std::cout << "within range" << std::endl;

If you want to execute several statements, you can use braces ({}) to define a code block.

The condition is a logical expression and C++ will convert from numeric types to a bool, where 0 is false and anything not 0 is true. If you are not careful, this can be a source of an error that is not only difficult to notice, but also can have an unexpected side-effect. Consider the following code, which asks for input from the console and then tests to see if the user enters -1:

    int i; 
    std::cin >> i; 
    if (i == -1) std::cout << "typed -1" << endl; 
    std::cout << "i = " << i << endl;

This is contrived, but you may be asking for values in a loop and then performing actions on those values, except when the user enters -1, at which point the loop finishes. If you mistype, you may end up with the following code:

    int i; 
    std::cin >> i; 
    if (i = -1) std::cout << "typed -1" << endl; 
    std::cout << "i = " << i << endl;

In this case, the assignment operator (=) is used instead of the equality operator (==). There is just one character difference, but this code is still correct C++ and the compiler is happy to compile it.

The result is that, regardless of what you type at the console, the variable i is assigned to -1, and since -1 is not zero, the condition in the if statement is true, hence the true clause of the statement is executed. Since the variable has been assigned to -1, this may alter logic further on in your code. The way to avoid this bug is to take advantage of the requirement that in an assignment the left-hand side must be an lvalue. Perform your test as follows:

    if (-1 == i) std::cout << "typed -1" << endl;

Here, the logical expression is (-1 == i), and since the == operator is commutative (the order of the operands does not matter; you get the same result), this is exactly the same as you intended in the preceding test. However, if you mistype the operator, you get the following:

    if (-1 = i) std::cout << "typed -1" << endl;

In this case, the assignment has an rvalue on the left-hand side, and this will cause the compiler to issue an error (in Visual C++ this is C2106 '=' : left operand must be l-value).

You are allowed to declare a variable in an if statement, and the scope of the variable is in the statement blocks. For example, a function that returns an integer can be called as follows:

    if (int i = getValue()) {    
        // i != 0    // can use i here  
    } else {    
        // i == 0    // can use i here  
    }

While this is perfectly legal C++, there are a few reasons why you would want to do this.

In some cases, the conditional operator ?: can be used instead of an if statement. The operator executes the expression to the left of the ? operator and, if the conditional expression is true, it executes the expression to the right of the ?. If the conditional expression is false, it executes the expression to the right of the :. The expression that the operator executes provides the return value of the conditional operator.

For example, the following code determines the maximum of two variables, a and b:

    int max; 
    if (a > b) max = a; 
    else       max = b;

This can be expressed with the following single statement:

    int max = (a > b) ? a : b;

The main choice is whichever is most readable in the code. Clearly, if the assignment expressions are large it may well be best to split them over lines in an if statement. However, it is useful to use the conditional statement in other statements. For example:

    int number;  
    std::cin  >> number; 
    std::cout << "there " 
              << ((number == 1) ? "is " : "are ")  
              << number << " item"            
              << ((number == 1) ? "" : "s") 
              << std::endl;

This code determines if the variable number is 1 and if so it prints on the console there is 1 item. This is because in both conditionals, if the value of the number variable is 1, the test is true and the first expression is used. Note that there is a pair of parentheses around the entire operator. The reason is that the stream << operator is overloaded, and you want the compiler to choose the version that takes a string, which is the type returned by the operator rather than bool, which is the type of the expression (number == 1).

If the value returned by the conditional operator is an lvalue then you can use it on the left-hand side of an assignment. This means that you can write the following, rather odd, code:

    int i = 10, j = 0; 
    ((i < j) ? i : j) = 7; 
    // i is 10, j is 7 

    i = 0, j = 10; 
    ((i < j) ? i : j) = 7; 
    // i is 7, j is 10

The conditional operator checks to see if i is less than j and if so it assigns a value to i; otherwise, it assigns j with that value. This code is terse, but it lacks readability. It is far better in this case to use an if statement.

Selecting

If you want to test to see if a variable is one of several values, using multiple if statements becomes cumbersome. The C++ switch statement fulfills this purpose much better. The basic syntax is shown here:

    int i; 
    std::cin >> i; 
    switch(i) 
    { 
        case 1:  
            std::cout << "one" << std::endl; 
            break; 
        case 2:  
            std::cout << "two" << std::endl; 
            break; 
        default: 
            std::cout << "other" << std::endl; 
    }

Each case is essentially a label as to the specific code to be run if the selected variable is the specified value. The default clause is for values where there exists no case. You do not have to have a default clause, which means that you are testing only for specified cases. The default clause could be for the most common case (in which case, the cases filter out the less likely values) or it could be for exceptional values (in which case, the cases handle the most likely values).

A switch statement can only test integer types (which includes enum), and you can only test for constants. The char type is an integer, and this means that you can use characters in the case items, but only individual characters; you cannot use strings:

    char c; 
    std::cin >> c; 
    switch(c) 
    { 
        case 'a':  
            std::cout << "character a" << std::endl; 
            break; 
        case 'z':   
            std::cout << "character z" << std::endl; 
            break; 
        default: 
            std::cout << "other character" << std::endl; 
    }

The break statement indicates the end of the statements executed for a case. If you do not specify it, execution will fall through and the following case statements will be executed even though they have been specified for a different case:

    switch(i) 
    { 
        case 1:  
            std::cout << "one" << std::endl; 
            // fall thru 
        case 2:  
            std::cout << "less than three" << std::endl; 
            break; 
        case 3:  
            std::cout << "three" << std::endl; 
            break; 
        case 4: 
            break; 
            default: 
            std::cout << "other" << std::endl; 
    }

This code shows the importance of the break statement. A value of 1 will print both one and less than three to the console, because execution falls through to the preceding case, even though that case is for another value.

It is usual to have different code for different cases, so you will most often finish a case with break. It is easy to miss out a break by mistake, and this will lead to unusual behavior. It is good practice to document your code when deliberately missing out the break statement so that you know that if a break is missing, it is likely to be a mistake.

You can provide zero or more statements for each case. If there is more than one statement, they are all executed for that specific case. If you provide no statements (as for case 4 in this example) then it means that no statements will be executed, not even those in the default clause.

The break statement means break out of this code block, and it behaves like this in the loop statements while and for as well. There are other ways that you can break out of a switch. A case could call return to finish the function where the switch is declared; it can call goto to jump to a label, or it can call throw to throw an exception that will be caught by an exception handler outside the switch, or even outside the function.

So far, the cases are in numeric order. This is not a requirement, but it does make the code more readable, and clearly, if you want to fall through the case statements (as in case 1 here), you should pay attention to the order the case items.

If you need to declare a temporary variable in a case handler then you must define a code block using braces, and this will make the scope of the variable localized to just that code block. You can, of course, use any variable declared outside of the switch statement in any of the case handlers.

Since enumerated constants are integers, you can test an enum in a switch statement:

    enum suits { clubs, diamonds, hearts, spades }; 

    void print_name(suits card) 
    { 
        switch(card) 
        { 
            case suits::clubs: 
                std::cout << "card is a club"; 
                break; 
            default: 
                std::cout << "card is not a club"; 
        } 
    }

Although the enum here is not scoped (it is neither enum class nor enum struct), it is not required to specify the scope of the value in the case, but it makes the code more obvious what the constant refers to.

Looping

Most programs will need to loop through some code. C++ provides several ways to do this, either by iterating with an indexed value or testing a logical condition.

Looping with Iteration

There are two versions of the for statement, iteration and range-based. The latter was introduced in C++11. The iteration version has the following format:

    for (init_expression; condition; loop_expression) 
        loop_statement;

You can provide one or more loop statements, and for more than one statement, you should provide a code block using braces. The purpose of the loop may be served by the loop expression, in which case you may not want a loop statement to be executed; here, you use the null statement, ; which means do nothing.

Within the parentheses are three expressions separated by semicolons. The first expression allows you to declare and initialize a loop variable. This variable is scoped to the for statement, so you can only use it in the for expressions or in the loop statements that follow. If you want more than one loop variable, you can declare them in this expression using the comma operator.

The for statement will loop while the condition expression is true; so if you are using a loop variable, you can use this expression to check the value of the loop variable. The third expression is called at the end of the loop, after the loop statement has been called; following this, the condition expression is called to see if the loop should continue. This final expression is often used to update the value of the loop variable. For example:

    for (int i = 0; i < 10; ++i)   
    { 
        std::cout << i; 
    }

In this code, the loop variable is i and it is initialized to zero. Next, the condition is checked, and since i will be less than 10, the statement will be executed (printing the value to the console). The next action is the loop expression; ++i, is called, which increments the loop variable, i, and then the condition is checked, and so on. Since the condition is i < 10, this means that this loop will run ten times with a value of i between 0 and 9 (so you will see 0123456789 on the console).

The loop expression can be any expression you like, but often it increments or decrements a value. You do not have to change the loop variable value by 1; for example, you can use i -= 5 as the loop expression to decrease the variable by 5 on each loop. The loop variable can be any type you like; it does not have to be integer, it does not even have to be numeric (for example, it could be a pointer, or an iterator object described in Chapter 5, Using the Standard Library Containers), and the condition and loop expression do not have to use the loop variable. In fact, you do not have to declare a loop variable at all!

If you do not provide a loop condition then the loop will be infinite, unless you provide a check in the loop:

for (int i = 0; ; ++i)  
{ 
   std::cout << i << std::endl; 
   if (i == 10) break; 
}

This uses the break statement introduced earlier with the switch statement. It indicates that execution exits the for loop, and you can also use return, goto, or throw. You will rarely see a statement that finishes using goto; however, you may see the following:

for (;;)  
{ 
   // code 
}

In this case, there is no loop variable, no loop expression, and no conditional. This is an everlasting loop, and the code within the loop determines when the loop finishes.

The third expression in the for statement, the loop expression, can be anything you like; the only property is that it is executed at the end of a loop. You may choose to change another variable in this expression, or you can even provide several expressions separated by the comma operator. For example, if you have two functions, one called poll_data that returns true if there is more data available and false when there is no more data, and a function called get_data that returns the next available data item, you could use for as follows (bear in mind; this is a contrived example, to make a point):

for (int i = -1; poll_data(); i = get_data()) 
{ 
   if (i != -1) std::cout << i << std::endl; 
}

When poll_data returns a false value, the loop will end. The if statement is needed because the first time the loop is called, get_data has not yet been called. A better version is as follows:

for (; poll_data() ;) 
{ 
   int i = get_data();  
   std::cout << i << std::endl; 
}

Keep this example in mind for the following section.

There is one other keyword that you can use in a for loop. In many cases, your for loop will have many lines of code and at some point, you may decide that the current loop has completed and you want to start the next loop (or, more specifically, execute the loop expression and then test the condition). To do this, you can call continue:

for (float divisor = 0.f; divisor < 10.f; ++divisor)  
{ 
   std::cout << divisor; 
   if (divisor == 0)  
   {  
      std::cout << std::endl; 
      continue; 
   } 
   std::cout << " " << (1 / divisor) << std::endl; 
}

In this code, we print the reciprocal of the numbers 0 to 9 (0.f is a 4-byte floating-point literal). The first line in the for loop prints the loop variable, and the next line checks to see if the variable is zero. If it is, it prints a new line and continues, that is, the last line in the for loop is not executed. The reason is that the last line prints the reciprocal and it would be an error to divide any number by zero.

C++11 introduces another way to use the for loop, which is intended to be used with containers. The C++ standard library contains templates for container classes. These classes contain collections of objects, and provide access to those items in a standard way. The standard way is to iterate through collections using an iterator object. More details about how to do this will be given in Chapter 5, Using the Standard Library Containers; the syntax requires an understanding of pointers and iterators, so we will not cover them here. The range-based for loop gives a simple mechanism to access items in a container without explicitly using iterators.

The syntax is simple:

for (for_declaration : expression) loop_statement;

The first thing to point out is that there are only two expressions and they are separated by a colon (:). The first expression is used to declare the loop variable, which is of the type of the items in the collection being iterated through. The second expression gives access to the collection.

Note

In C++ terms, the collections that can be used are those that define a begin and end function that gives access to iterators, and also to stack-based arrays (that the compiler knows the size of).

The Standard Library defines a container object called a vector. The vector template is a class that contains items of the type specified in the angle brackets (<>); in the following code, the vector is initialized in a special way that is new to C++11, called list initialization. This syntax allows you to specify the initial values of the vector in a list between curly braces. The following code creates and initializes a vector, and then uses an iteration for loop to print out all the values:

using namespace std; 
vector<string> beatles = { "John", "Paul", "George", "Ringo" }; 

for (int i = 0; i < beatles.size(); ++i)  
{ 
   cout << beatles.at(i) << endl; 
}

Note

Here a using statement is used so that the classes vector and string do not have to be used with fully qualified names.

The vector class has a member function called size (called through the . operator, which means "call this function on this object") that returns the number of items in the vector. Each item is accessed using the at function passing the item's index. The one big problem with this code is that it uses random access, that is, it accesses each item using its index. This is a property of vector, but other Standard Library container types do not have random access. The following uses the range-based for:

vector<string> beatles = { "John", "Paul", "George", "Ringo" }; 

for (string musician : beatles)  
{ 
   cout << musician << endl; 
}

This syntax works with any of the standard container types and for arrays allocated on the stack:

int birth_years[] = { 1940, 1942, 1943, 1940 }; 

for (int birth_year : birth_years)  
{ 
   cout << birth_year << endl; 
}

In this case, the compiler knows the size of the array (because the compiler has allocated the array) and so it can determine the range. The range-based for loop will iterate through all the items in the container, but as with the previous version you can leave the for loop using break, return, throw, or goto, and you can indicate that the next loop should be executed using the continue statement.

Conditional Loops

In the previous section we gave a contrived example, where the condition in the for loop polled for data:

for (; poll_data() ;) 
{ 
   int i = get_data();  
   std::cout << i << std::endl; 
}

In this example, there is no loop variable used in the condition. This is a candidate for the while conditional loop:

while (poll_data()) 
{ 
   int i = get_data();  
   std::cout << i << std::endl; 
}

The statement will continue to loop until the expression (poll_data in this case) has a value of false. As with for, you can exit the while loop with break, return, throw, or goto, and you can indicate that the next loop should be executed using the continue statement.

The first time the while statement is called, the condition is tested before the loop is executed; in some cases you may want the loop executed at least once, and then test the condition (most likely dependent upon the action in the loop) to see if the loop should be repeated. The way to do this is to use the do-while loop:

int i = 5; 
do 
{ 
   std::cout << i-- << std::endl; 
} while (i > 0);

Note the semicolon after the while clause. This is required.

This loop will print 5 to 1 in reverse order. The reason is that the loop starts with i initialized to 5. The statement in the loop decrements the variable through a postfix operator, which means the value before the decrement is passed to the stream. At the end of the loop, the while clause tests to see if the variable is greater than zero. If this test is true, the loop is repeated. When the loop is called with i assigned to 1, the value of 1 is printed to the console and the variable decremented to zero, and the while clause will test an expression that is false and the looping will finish.

The difference between the two types of loop is that the condition is tested before the loop is executed in the while loop, and so the loop may not be executed. In a do-while loop, the condition is called after the loop, which means that, with a do-while loop, the loop statements are always called at least once.

Jumping

C++ supports jumps, and in most cases, there are better ways to branch code; however, for completeness, we will cover the mechanism here. There are two parts to a jump: a labeled statement to jump to and the goto statement. A label has the same naming rules as a variable; it is declared suffixed with a colon, and it must be before a statement. The goto statement is called using the label's name:

    int main() 
    { 
        for (int i = 0; i < 10; ++i) 
        { 
            std::cout << i << std::endl; 
            if (i == 5) goto end; 
        } 

    end:
        std::cout << "end"; 
    }

The label must be in the same function as the calling goto.

Jumps are rarely used, because they encourage you to write non-structured code. However, if you have a routine with highly nested loops or if statements, it may make more sense and be more readable to use a goto to jump to clean up code.