APPENDIX A

image

C++11/14 Features

C++ is a language in constant evolution. Since its first release in the 1980s, new concepts and techniques that started as research topics became an integral part of the language. The latest revision of the C++ standard is C++14, which is itself a minor addition to the C++11 standard. These two updates to C++ are already part of major compilers, so it is important to understand what these modifications bring for developers.

In this appendix, I provide a summary of the most important changes recently introduced in the C++ standard. You will learn about the following topics:

  • auto-typed variables: a syntax that allows automatic type detection.
  • Lambdas: creating functions in place and sharing variables from a local environment.
  • User-defined literals: creating literals with user-defined behavior.
  • Range-based for: a new form of the for loop, which simplifies container manipulation.
  • Rvalue references: a new technique to implement “move semantics” into user-defined types.
  • New function declarator syntax: a syntax for function where the return type is automatically detected.
  • Delegating constructors: how to delegate class initialization to a single constructor.
  • Inheriting constructors: directly using constructors defined in a parent class.
  • Generalized attributes: how to declare attributes for C++ elements using a unified syntax.
  • Generalized constant expressions: defining expressions that can be used at compilation time by other expressions, including templates.
  • Null pointer constant: a new constant that uniquely defines a null pointer.
  • Right-angle brackets: a simplification of template syntax, avoiding common confusions with the shift operator.
  • Initializer lists: a general way to perform initialization of C++ variables.

Automatic Type Detection

One of the main features of C++ is the use of types to check the program during compilation time. This feature, known as strong type checking, allows programmers to rely on the compiler to find cases where variables are assigned values of incompatible types, for example. With this information, developers can uncover bugs that would take a lot of time to remove otherwise. It is generally accepted that strong type checking is a useful feature, especially for large-scale projects, where hundreds or even thousands of classes can be made available.

Although type checking is so important for C++ practitioners, the need to name types at each variable and function declaration has become too burdensome for some programmers. After all, every expression in C++ has a type, and with the introduction of containers and other templates, it may sometimes be difficult to write the proper type of a complex expression. To avoid this problem, the C++11 committee decided to use the auto keyword to allow for automatic type detection in C++ expressions.

Automatic type deduction frees programmers from the need to indicate the type of each variable when declaring it. The type deduction system works through the use of information that is already available to the compiler at the moment an expression is being parsed. For example, if a variable is created from a known constant, the compiler can easily determine its type. On the other hand, if a variable is initialized with the result of an expression, the compiler can also determine the type of the result and use it for the variable. Following are some simple examples:

void autoExample()
{
    auto i = 1;          // this is an integer
    auto d = 2.0;        // this is a float
    auto d2 = d + 1;     // this is also a float
    auto str = "hello";  // this is a char[6]

    cout << "integer : " << i << " float: "  << d2 << " string " << str << endl;
}

Here, the first, second, and fourth variables are initialized using constants, so the type is immediate. The third variable has its type determined through the result of the expression given as the initializer.

Another area where auto variables are very useful is when working with templates. Many templates generate complex types, which are difficult to type and to remember. It is very useful to be able to avoid typing these types with the help of the auto keyword. Following is an example using an iterator to an STL (standard template library) container:

void autoTemporaryExamp()
{
    std::vector<std::pair<int,std::string>> myVector;

    // without auto
    for (std::vector< std::pair<int,std::string>>::iterator it = myVector.begin();
         it != myVector.end(); ++it)
    {
        // do something here
    }

    // with auto
    for (auto it = myVector.begin(); it != myVector.end(); ++it)
    {
        // same thing here
    }
}

The first loop shows the type of the iterator used to visit all members of the container. It is even harder to type than the original template name. The second loop shows how to express the same thing using the auto keyword. Here, it is possible to avoid the name of the template, which makes it much easier to understand what the code is doing.

Lambdas

A lambda is a function that can be created on the spot, without the need for a separate top-level declaration. Lambda functions can, additionally, be allowed to retain references to variables that exist at the same level in which they are introduced. A lambda function can be saved in variables and passed to other functions, where they can be used as needed. The variables that have been saved in such a lambda context can be used even after the original block has finished. Here is a very simple example.

void lambdaExample()
{
    auto avg = [](int a, int b) { return (a + b) / 2; }; // the lambda expression

    cout << "the average of 3 and 5 is "  << avg(3, 5) << endl;
}

As seen in the example, the syntax for lambda functions starts with a pair of square brackets. The return type doesn’t need to be specified, and it is deduced from the variable or expression in the return keyword. Variables from the outside environment used inside the lambda function should be listed inside the square brackets—this is called variable capture. If a captured variable is preceded by an ampersand sign (&), this means that the variable can be modified. The following is an example of local variable capture:

void lambdaExample2()
{
    double factor = 2.5;
    auto scaledAvg = [&factor](int a, int b) { return factor * (a + b) / 2; };

    auto modifiedAvg = [&](int a, int b) { return scaledAvg(a, b); };

    cout << "the scaled average of 3 and 5 is "  << scaledAvg(3, 5) << endl;
    cout << "this should be the same "  << modifiedAvg(3, 5) << endl;
}

The example shows two lambda functions where there is variable capture. In the first function the factor variable is captured, and it becomes available to be used inside the lambda function. The second example shows a lambda function where all local variables are captured (indicated by the [&] notation). In this case, any local variable can be used, including the scaledAvg variable.

User-Defined Literals

You are familiar with literals for standard types such as int, float, or char. These literal values allow one to initialize new variables as needed. C++11 introduces user-defined literals, where a literal can be manipulated to perform any kind of preprocessing. This is useful in case scalar numbers need to go though some kind of conversion before they are used as initializers.

The syntax used for user-defined literals is similar to other operators. The operator "" keyword is used to introduce the new literal format. Consider an example where you wish to define numeric literals that return the price in Euros. This can be defined in the following way:

long double operator "" _eu(long double val)
{
    return val / 1.24;
}

Notice that the signature contains the name operator "", followed by the suffix _eu. In this case you’ll be using a fixed conversion value, but in general you could have a more complex scheme for conversion from dollars to euros. Finally you can use this user-defined literal in the following way:

void showUserDefinedLiterals()
{
    double price = 300; // price in dollars
    long double priceInEU =  300.0_eu;

    cout << " price in dollars: "  << price
    << " price in Euros: " << priceInEU << endl;
 }

Here, you first define a price without any conversion (in dollars). Then you create a second variable that correspond to the same quantity, but using the user-defined suffix _eu. Using this suffix, you will have a converted price in the priceInEU variable, as printed at the end of the showUserDefinedLiterals function.

Range-based for

STL containers are some of the most used templates in any C++ system. These containers are versatile and can be used to perform a large number of operations on its components. In the previous versions of C++, it was possible to iterate through the components of a container using an auxiliary iterator variable. For example,

void loopExample1()
{
    std::vector<std::pair<double,std::string>> v;

    // without auto
    for (std::vector<std::pair<double,std::string>>::iterator it = v.begin();
         it != v.end(); ++it)
    {
        // do something here
    }
}

Or, you can use an auto variable to simplify the previous code a little. Still, there is a lot of code necessary just to iterate over the elements of the container. The C++11 standard introduces a simpler way to do this, with the container-oriented for loop. The syntax for this special case is simplified, so you don’t need to write the boundary conditions (begin() and end()) for the container. Here is the previous example, modified to use the new for loop.

void forLoopExample()
{
    std::vector<std::pair<double,std::string>> vectorOfPairs;

    for (auto &i : vectorOfPairs)
    {
        cout << " values are "
             << i.first << " and "
             << i.second << endl;
    }
}

Notice how the vectorOfPairs variable is now used only once in the second part of the loop statement. The auto variable declaration avoids the need for a long template declaration, which helps to keep the notation easy to read.

Rvalue References

One of the common performance issues with the use of containers and strings in C++ is the fact that temporary variables need to be created in so many places:

  • When moving elements between two containers, it is frequently necessary to perform a copy and then delete the old elements.
  • When implementing operators, it is often necessary to return new objects each time an operation is performed, since the argument to an operator (such as <<) may very well be a temporary object.
  • When returning objects from functions, it becomes necessary to copy the return object to a temporary, since it belonged to a function that is finishing. If this temporary object is immediately assigned to a new variable, then the temporary object is not used.

To help developers to tackle these issues, C++ designers decided to introduce a notation for variables that are not named and that cannot be assigned outside the current context. Such variables are known as rvalues, because in any expression they can only appear in the right side of the assignment. Examples of rvalues are immediate values passed as parameters to functions, and temporaries created during the evaluation of expressions, among others.

The syntax for rvalues is similar to references, but with the && sign used instead of a single & sign. Such declarations are useful mainly in the list of arguments for a function, as well as in the return type. Following are some examples of their use:

void rvalExamp(string &&s)
{
    cout << " string is " << s << endl;
}

void rvalExamp(string &s)
{
    cout << " string lvalue: " << s << endl;
}

int main()
{
    rvalExamp("a test string");  // calls rval version
    string a = "string a ";
    string b = "string b ";
    rvalExamp(a + b);            // calls rval version
    string c = "another example";
    rvalExamp(c);                // calls lval version
    return 0;
}

In this example, any string (including temporary values) can be passed to the function rvalExamp. The rvalue may be used with the knowledge that its temporary value will be destroyed at the end of the function. On the other hand, you can also have a version of the function that receives a standard lvalue reference. This version of the function is called only when an lvalue is used as parameter (in this case it happens when the parameter is a named variable).

An important case where rvalues may be useful is in the assignment operator. If the parameter to the operator is an rvalue, then it is usually possible to optimize it by reducing the number of allocations. This is shown in the following example:

class RValTest {
public:
    RValTest(int n);
    RValTest(const RValTest &x);
    ~RValTest();

    RValTest &operator=(RValTest &&p);  // this is for RVAL
    RValTest &operator=(RValTest &p);   // this is for LVAL
private:
    vector<int> data;
};

RValTest::RValTest(int n)
: data(n, 0)
{

}

RValTest::RValTest(const RValTest &p)
: data(p.data)
{
}

RValTest::~RValTest()
{
}

RValTest &RValTest::operator=(RValTest &&p)
{
    data.swap(p.data);
    cout << " calling rval assignment " << endl;
    return *this;
}

RValTest &RValTest::operator=(RValTest &p)
{
    if (this != &p)
    {
        data = p.data;
    }
    cout << " calling normal assignment " << endl;
    return *this;
}

void useRValTest()
{
    RValTest test(3);
    RValTest test2(4);

    test2 = test;        // use standard assignment
    test = RValTest(5);  // use rval assignment
}

The class RValTest knows when the assignment operator is called with a temporary. In this case, you can just swap the elements of the data array, instead of performing expensive data copy.

New Function Declarator Syntax and Decltype

You have seen that the keyword auto was repurposed to allow for automatic type deduction or variables. However, once this change has been made to how variables are declared, you will also need to be able to return such values. For example, consider the following function:

void autoFunctExample1(vector<int> &x)
{
    auto iterator = x.begin();
    // do something with iterator
}

This works fine, and you don’t need to know the exact template type returned by begin() to use it. However, a big problem arises if you need to return the variable iterator. In that case, you need to somehow determine the type of iterator just to declare the function, since the return type must be part of the signature.

To help solve this problem, C++11 introduced a new form of function declaration, which uses auto instead of the name of the type. Still, to maintain the type checking system the compiler needs to determine the type of a function. This is where the decltype keyword comes in. The decltype operator returns the type of any expression that is given as a parameter. Similarly to how sizeof returns information from a type, decltype returns the type for a variable or other general expression.

Using decltype, you can now add a return type declaration to a function after the -> operator, which may only appear right after the list of arguments to the function. Since at this point the type of the arguments to the function is known, you can use it along with decltype to define the return type. Following is an example based on the previous code :

auto autoFuncExample(vector<int> &x) -> decltype(x.begin())
{
    auto iterator = x.begin();
    // do something with iterator
    return iterator;
}

Now you can return the iterator without knowing its exact type, since it is automatically calculated during compilation time.

Image Note  The decltype operator is not restricted to appear in the declaration of a return type. You can use it anywhere a type may be required, although many times it can be substituted by the auto keyword. For example, the variable declaration auto x = 1 is equivalent to decltype(1) x = 1. But decltype can be used in other contexts, such as sizeof(decltype(x.begin())), to determine the size of a deduced type, where auto would not work.

Delegating Constructors

In older versions of C++, the problem of creating and maintaining initializers along with constructors was well known. For example, you needed to initialize all scalar variables in the same order that they appear in the class declaration. C++11 avoids this issue by delegating the task of data initializing to other constructors.

A delegating constructor is simply one that can be used by other constructors to avoid the repetition of data initialization statements. For example, suppose you have a class Dimensions with three member variables. You can have three different constructors, each one accepting a different number of components for this dimension object. To avoid repeating yourself during the initialization part, you can create a single initializer constructor, and call this constructor from the others. Following is a possible implementation using C++11:

class Dimensions {
public:
    Dimensions();
    Dimensions(double x);
    Dimensions(double x, double y);
    Dimensions(double x, double y, double z);

private:
    double m_x;
    double m_y;
    double m_z;
};

Dimensions::Dimensions()
: Dimensions(0, 0, 0)
{
}

Dimensions::Dimensions(double x)
: Dimensions(x, 0, 0)
{
}

Dimensions::Dimensions(double x, double y)
: Dimensions(x, y, 0)
{
}

Dimensions::Dimensions(double x, double y, double z)
: m_x(x),
  m_y(y),
  m_z(z)
{
}

The constructor Dimensions(double x, double y, double z) is the only one that can access the member variables directly, while the others are only using it to perform indirect initialization.

Inheriting Constructors

Another common problem in earlier versions of C++ was the handling of constructors in derived classes. Sometimes, a constructor derived from a class has constructors that are identical to the constructors in the superclass. In this case, it was necessary to replicate all constructors in the subclass so that they would become available to clients. If seems clear that this is an undesirable code replication, and it was addressed by the C++11 standard. Now, it is possible to employ the using keyword to introduce the constructors existing in the base class. Here is an example, using the Dimensions class as its base.

class DimensionsDerived : public Dimensions {
public:
    using Dimensions::Dimensions;

};

int main()
{
    DimensionsDerived(1, 2, 4);
}

The new class can be created using the same constructors as the parent, since it contains the using declaration for the base constructor.

Generalized Attributes

Attributes provide a standard syntax for the addition of annotations to elements contained in C++ code. Most compilers use nonstandard mechanisms to determine the attributes of certain elements. For example, if a function can be exported or not is defined by attributes, which vary according to the compiler vendor.

C++14 introduced a new syntax for attributes that can be used by any compiler vendor. The attributes are listed inside double brackets, and they contain an annotation that is applied to the element located syntactically next to the attribute. Following is an example:

struct [[exported]] AttribSample
{
    int memberA;
    [[gnu::aligned (16)]]
    double memberB;
};

Image Note  The list of available attributes is specific to each compiler. However, at least for gcc it is possible to write custom plug-ins that are able to process these attributes. For example, suppose that you create a plug-in to process GUI-based classes in your code base. Running gcc with that plug-in will let you to perform actions for each GUI class, such as generating additional code, creating resources files to be used during runtime, and other related tasks.

Generalized Constant Expressions

In modern C++, there is a great deal of emphasis on the use of templates and related compile-time programming techniques. The STL and many other well-known libraries, such as boost, depend heavily on templates. However, since template-based operations are compile-time by definition, they introduce the need for constant, compile-time evaluated expressions. Such expressions have in common the fact that they evaluate to constant values, so that all the results will be available at compilation time.

While normal C++ code can involve both runtime and compilation-time expressions, it useful to guarantee that the value in a particular function is completely evaluated at compilation time. This cannot be guaranteed with traditional functions, however, which motivated the standards committee to introduce constant expressions as a compiler-enforced concept.

To guarantee that a function will evaluate only to constants that are available at compiler time, you should use the new constexpr keyword. When this keyword is added before a function declaration, the compiler will force its evaluation and emit an error if the included expressions cannot be calculated at compile time. Here is a simple example.

struct TestStruct {
    int a;
    char b;
    double c;
};

template <class T>
constexpr int testDataSize(T)
{
    return sizeof(T);
}

constexpr int minTestSize()
{
    return 2 * testDataSize(TestStruct()) + 1;
}

The testDataSize function just shows how easy it is to create a compile-time function. The return value calculated in the first function is the size of a test data structure, which can later be used by other constant calculations. The second function just calculates what is considered the minimum size for the test data in the application. Results such as the ones presented earlier can be freely used on templates, as a way to perform more complex calculations.

Null Pointer Constant

A null pointer is a pointer that doesn’t correspond to any valid address in the target machine. Traditionally, null pointer values have been used to indicate that a pointer is not in use. For a function’s return value, this usually means that the resulting pointer is invalid, among other possible uses.

C++ inherited from C the idea that null pointers are equivalent to the constant 0, since this is an invalid pointer value in most computer architectures. In fact, the preprocessor macro NULL is defined in previous versions of C and C++ as 0. The fact that the 0 value can be confused with NULL in a numeric context, however, is one of the problems inherent to this definition. To simplify the rules concerning null pointers, the C++ committee decided to introduce a new keyword, nullptr, which can only be interpreted as a pointer and not an integer or any other type that is related to the 0 constant.

void *testNull()
{
    int *pi = new int;
    if (pi == nullptr)
    {
        return nullptr;
    }
    // *pi = 1 + nullptr; // this doesn't work, nullptr is not an integer
    return pi;
}

The previous code checks if a newly allocated variable is null. Notice that the nullptr value cannot be used to simultaneously initialize an integer variable: it can only be used in a pointer context.

Defaulted and Deleted Member Functions

Another new feature in C++ is the ability to clearly determine if a class will use or disallow any of the default member functions provided by the compiler. Remember that there are four member functions automatically provided when a class is created:

  • The default constructor
  • The copy constructor
  • The destructor
  • The assignment operator

Standard practice indicates that you should define these functions for every new type, as you can see in the examples presented in this book. However, C++ gives another option: you can use the default and delete keywords to determine which of these member functions can be used by default (with the version created by the compiler) and which versions should be discarded. For example,

class TestDefaults {
public:
    TestDefaults() = default;
    TestDefaults(int arg);
    TestDefaults(const TestDefaults &) = delete;

    // other member functions here
};

This class uses a default constructor, whose definition is written automatically by the compiler, even though it has a nondefault constructor that receives a single integer argument. This was not possible in previous versions of C++, where you could either accept the default constructor or write it again in case you wanted two or more constructors. Notice that you can, at the same time, reject the default copy constructor. Therefore, the previous declaration directly indicates that the type cannot be copied.

Another useful feature of default member functions is that you can introduce virtual destructors without the need to write one. Remember that classes that include virtual member functions also require virtual destructors in order to clean up resources in each of the levels of the class hierarchy. The standard way of doing this is introducing an empty virtual destructor, in order to allow for virtual destructors in the subclasses. In C++11 you can use the default keyword to provide a default, virtual destructor. In a previous example this could be added in the following way:

class TestDefaults {
public:
    TestDefaults() = default;
    TestDefaults(int arg);
    virtual ~TestDefaults() = default;
    TestDefaults(const TestDefaults &) = delete;

    // other member functions here
};

Notice that you don’t need to explicitly write the destructor, since it will use the default implementation. The derived classes, however, will enjoy the use of a virtual destructor due to the definition in the base class.

Right-Angle Brackets

This is a minor extension to older versions of C++, but it is nonetheless noteworthy. Before C++11, angle brackets were used to enclose template types, but they could be confused with the >> operator—for example, the declaration

vector<map<string, int>> myVector;

This has the subtle bug of introducing the >> operator in the middle of a declaration. To fix that, you had to write

vector<map<string, int> > myVector;

which is the same expression with an extra space between angle brackets. In C++11, however, a combination of two or more closing angle brackets is correctly recognized as enclosing a type declaration.

Initializer Lists

One of the confusing aspects of C++ syntax is variable initialization. Different objects, such as integers, structures, classes, and arrays, have slightly different ways to be initialized. C++11, while maintaining the previous methods for variable initialization, introduces a new way to perform initialization that is much more regular and can be applied to any object in the language.

The syntax for initialization lists uses braces to surround one or more constants or variables. These elements are then applied to the new variable and interpreted according to its type. Following are a few examples:

void initializationTest()
{
    int x {}; // equivalent to int x = 0;
    int y { 0 };  // same as above
    const char *s { "var"  };
    double d { 2.4 };
    struct StrTest {
        int a;
        double d;
        char c;
    };

    StrTest structVal { 2, 4.2, 'f' };

    cout << " values are "  << x << "  " << y << " "  << s
         << " "  << d << " " << structVal.a << endl;

    class AClass {
    public:
        AClass(int v) : m_val(v) {}
        int m_val;
    };

    AClass obj = { 3 } ;
}

Notice that all these values can be easily initialized using the brace notation. Among the advantages of this strategy is the fact that you can also initialize containers (such as vectors) using lists of values enclosed in braces. Following is an example of this feature:

void containerInitialization()
{
    vector<int> vi = { 1, 3, 5, 7, 9, 11 };

    for (auto &v : vi)
    {
        cout << v << " ";
    }

    map<int,double> m = { { 2, 3.0}, {4, 5.0} };
    for (auto &v : m)
    {
        cout << v.first << " " << v.second << " ";
    }
}

You can see from the previous example how initialization lists can be effectively used to pass data to standard containers found in the STL. Most containers in C++11 have one or more constructors that can receive initialization lists. Additionally, you can also create classes that receive lists of parameters, using the class std::initializer_list. The compiler will automatically fill the initializer_list container with the values passed to the constructor.

class MyClass {
public:
    MyClass(std::initializer_list<int> args);
    vector<int> m_vector;
};

MyClass::MyClass(std::initializer_list<int> args)
{
    m_vector.insert(m_vector.begin(), args.begin(), args.end());
}

void useClassInitializer()
{
    MyClass myClass = { 2, 5, 6, 22, 34, 25 };

    for (auto &v : myClass.m_vector)
    {
        cout << v << " ";
    }
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.107.25