The C++ Programming Language – Part 1

This post will be mostly for personal reference as I go through Bjarne Stroustrop’s “The C++ Programming Language” 4th edition textbook. Some of the notes will appear random.

Introduction

The purpose of a programming language is to express ideas in code. It provides a vehicle for development and execution, and provides a set of concepts for problem solving. C++ specifically provides direct mappings of built-in operations and types to hardware for efficiency and speed. It also provides abstraction mechanisms for user-defined type creation with the same support and performance as built-in types. The overall target is efficiency and elegance. C++ was originally the convergence of Simula and C.

C++ most directly supports 3 programming styles. First, procedural programming: processing and design of suitable data structures for the problem at hand. Second, object oriented: class hierarchies, runtime polymorphism. Lastly, generic programming: algorithms that accept N types, template programming. Most of these paradigms are built around data abstraction; hiding implementation details behind exposed interfaces.

The concept of lvalues and rvalues are crucial in C++. For example, int x = 5. lvalue of ‘x’ is reliable, it is located in memory at a trustworthy position. rvalue is an expression that does not represent an object occupying some identifiable location in memory. The difference between C and C++ is primarily the degree of emphasis on types and structure. C++ is a language you can grow with.

Lastly, how can you write good code in C++ (or any language)? Know what you want to express and practice (imitate good code).

Chapter 2

C++ is a statically typed language. The compiler can only determine the set of operations applicable to an entity by its type. the ISO C++ standard defines two things: core language features and standard library components. What is a declaration? A statement that introduces a name into the program and specifies a type for the name entity. A type defines the set of possible values and a set of operations for the entity. An object is some memory that holds a value of a type. A value is a set of bits interpreted to a type. A variable is a named object. C++ performs all meaningful conversions between basic types, but here be dragons for those unaware. Utilize {} during initialization to avoid conversion issues. Use . “dot” to access struct members through a name (reference), and -> “arrow” to access struct members through a pointer. For example:

void f(Vector v, Vector &rv, Vector *pv)
{
    int i1 = v.sz;
    int i2 = rv.sz;
    int i3 = pv->sz;
}

A feature in C++11, enum classes make enumerations both “strongly typed and strongly scoped.” For example, an enum class called TrafficLight containing the 3 states of a light, green, yellow, and red:

// g++ -std=c++11 -o enum_classes enum_classes.cpp

#include <iostream>
using namespace std;

enum class TrafficLight { green, 
                          yellow, 
                          red };

TrafficLight& operator++(TrafficLight& t)
{
    switch(t) {
        case TrafficLight::green:
            return t=TrafficLight::yellow;

        case TrafficLight::yellow:
            return t=TrafficLight::red;

        case TrafficLight::red:
            return t=TrafficLight::green;

        }
}

/* define the << operator for cout and TrafficLight! */
std::ostream& operator<<(std::ostream& out, TrafficLight t)
{
    switch(t)
    {
        case TrafficLight::green:
            out << "green";
            break;
        case TrafficLight::yellow:
            out << "yellow";
            break;
        case TrafficLight::red:
            out << "red";
            break;
        default:
            out << "wat";
            break;
    }

    return out;
}


int main()
{
    int light_cycle = 0;
    TrafficLight light = TrafficLight::red;

    while(light_cycle < 3) {
        cout << "Traffic light is " << light << "\n";
        ++light;
        light_cycle++;
    }
   
    return 0;
}

Output:

user@ubuntu:~/cpp/part_1/chapter_2$ ./enum_classes
Traffic light is red
Traffic light is green
Traffic light is yellow

Using the auto keyword allows a type to be determined at compile time. This is helpful if return types may change, allowing code with auto to avoid an update. The obvious trade-off to this convenience is loss of developer insight; he won’t know what type is being returned without looking at more code. Said a more formal way: “For variables, specifies that the type of the variable that is being declared will be automatically deduced from its initializer. For functions, specifies that the return type is a trailing return type or will be deduced from its return statements (since C++14) for non-type template parameters, specifies that the type will be deduced from the argument(since C++17).”:

#include <iostream>
using namespace std;


int main()
{
    /* types will be automatically inferred. 
     * this can help to avoid long type names, cluttering code. */
    auto b = true;
    auto ch = 'x';
    auto i = 123;
    auto d = 1.2;
    auto z = sqrt(y);

}

Const and constexprs confounded me at first. This example shows their use and I am 95% confident in the comments:

#include <iostream>
using namespace std;


double square(double x)
{
    return x*x;
}

int main()
{
    /* const: i promise not to change this value */
    const int dmv = 17; // named constant in RO memory
    int var = 17; // not a constant, goes on the stack

    /* constexpr: to be evaluated at compile time. place data in read-only memory.  */
    constexpr double max1 = 1.4*square(dmv); // ok if square(17) is a constant expression
    constexpr double max2 = 1.4*square(var); // var is not a constant expression, error.
    const double max3 = 1.4*square(var); // evaluated at runtime
}

The output of a compilation describes why the use of constexpr is incorrect in this instance. Note that this feature is specific to C++11.

user@ubuntu:~/cpp/part_1/chapter_2$ g++ -std=c++11 -o immutability immutability.cpp 
immutability.cpp: In function ‘int main()’:
immutability.cpp:17:39: error: call to non-constexpr function ‘double square(double)’
     constexpr double max1 = 1.4*square(dmv); // ok if square(17) is a constant expression
                                       ^
immutability.cpp:18:39: error: call to non-constexpr function ‘double square(double)’
     constexpr double max2 = 1.4*square(var); // var is not a constant expression, error.

Initialization in C++ can be done in many ways. Here are a few interesting examples. I note DRAGONS because type conversion issues can silently occur if you aren’t very careful.

#include <iostream>
using namespace std;


int main()
{
    double d_1 = 2.3;
    double d_2 {2.3};

    complex<double> z = 1;
    complex<double> z_2 {d1, d2};
    /* = is optional with {} initialization */
    complex<double> z_3 = {1, 2};

    vector<int> v {1, 2, 3, 4, 5, 6};

    /* ~DRAGONS~ */
    int i_1 = 7.2;  /* i_1 is 7 */
    int i_2 {7.2};  /* error */
    int i_3 = {7.2} /* error */

}

An invariant is something that is assumed to be true about a class. For example, “elem points to an array of ‘size’ doubles.” Constructors must establish invariants. This is very important because it makes us understand exactly what we want and it forces us to be specific.

Vector::Vector(int s)
{
    if (s < 0) {
        throw length_error{"Vector constructor size"};
    }
    elem = new double[s];
    sz = s;
}


/* Using the constructor: */
void test()
{
    try {
        Vector v(-27);
    }
    catch (std::length_error) {
        // ...
    }
    catch (std:bad_alloc) {
        // couldnt get heap space
    }
}

A namespace is a mechanism for expressing that some declarations belong together. Names shouldn’t clash with other names. In this example, a class is created named complex. We need to separate namespaces because complex already exists in the standard namespace.

namespace My_code
{
    class complex { /*...*/ }
    complex sqrt(complex);
    // ...
    int main();
}


int My_code::main()
{
    complex z {1, 2};
    auto z1 = sqrt(z);
    std::cout << "{" << z2.real() << "," << z2.imag() << "}\n";
    // ...
}


int main()
{
    return My_code::main();
}

C++ structs are straightforward. They provide a easy to use mechanism for encapsulating data. They can have methods as well. All data and methods in a C++ struct default to public. This contrasts with C++ classes which all data and methods default to private. This is key to the practice of object oriented design.

struct Vector {
    int sz;
    double *elem;
}

Vector v;

void vector_init(Vector &v, int s)
{
    v.elem = new double[s];
    v.sz = s;
}

Exceptions are provided mainly through the type system. Here is an example of error handling for out-of-bounds vector access. This at least makes the developer aware that strange/bad things are happening.

// g++ -O0 -ggdb -std=c++11 -o Vector_error_handling Vector_error_handling.cpp
/* Vector implementation with newly added error handling 
 * C++ provides error handling mainly through the Type system */

#include "Vector.h"
#include <iostream>
#include <stdexcept>

using namespace std;

Vector::Vector(int s)
{
    elem = new double[s];
    sz = s;
}


/* For example, lets ensure no out of bounds access
 * or at least make the user aware that it is happening */
double& Vector::operator[](int i)
{
    if (i < 0 || size() <= i) {
        /* throw an exception and hope the library code user
         * has implemented an exception handler
         * note: out_of_range type is defined in <stdexcept> */
        throw out_of_range{"Vector::operator[]"};
    }
    return elem[i];
}


int Vector::size() const
{
    return sz;
}

int main()
{
    Vector v(1000);
    double a = v[1]; // ok
    double b = v[2000]; //oob
    return 0;
}

Output:

user@ubuntu:~/cpp/part_1/chapter_2$ ./Vector_error_handling 
terminate called after throwing an instance of 'std::out_of_range'
  what():  Vector::operator[]
Aborted