CS 372 Spring 2016  >  Notes for January 25, 2016


CS 372 Spring 2016
Notes
for January 25, 2016

Modern C++: History

The C++ programming language was initially developed by Bjarne Stroustrup in 1983, as C with Classes.

The first ANSI/ISO standard was published in 1998. There was a minor update in 2003—bug/typo fixes only. These standards are referred to as C++98 and C++03, respectively.

A number of issues arose. In particular:

To deal with such issues, in the mid 2000s an effort to produce a major revision of the C++ Standard was initiated. This was termed C++0x, in the expectation that it would be finished prior to 2010, and so the “x” would be replaced with a single digit. However, the new standard was actually completed in 2011; it is now known as C++11.

The plan is now to release a new C++ standard every three years. So a programming language that was once pretty much fixed is now all a-ferment again. C++14 was a relatively minor update. The C++17 effort is now underway; it is aimed at making more significant revisions.

We will look at some of the features added in C++11, with a few comments about C++14.

Modern C++: Various Features

Hash Tables

C++11 adds new containers std::unordered_set, std::unordered_map, std::unordered_multiset, and std::unordered_multimap. The iterfaces of these are similar to std::set, std::map, etc., but the implementations are based on hash tables, rather than balanced search trees.

The unordered set containers are declared in standard header <unordered_set>, the unordered map containers are declared in standard header <unordered_map>,

See usehash.cpp for example code using C++11 hash-table containers.

Initializer Lists

C++ has always allowed built-in arrays to be initialized via a brace-enclosed list of values. This syntax was inherited from C.

[C++]

int arr[5] = { 6, 5, 4, 3, 2 };

C++11 extends this to arbitrary classes.

[C++]

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

The type of the brace-enclosed list is std::initializer_list<T>, where T is the type of each item in the list. This template is declared in standard header <initializer_list>. To use this initialization syntax with your own C++ class, write a constructor that takes an initializer_list as a parameter.

Fixed-Size Array Container: std::array

C++11 adds a new fixed-size array container: std::array, declared in standard header <array>.

[C++]

#include <array>
using std::array;

std::array works much like std::vector, except that it is not resizable, and the size is given as a template parameter.

[C++]

array<int, 8> aa = { 9, 8, 7, 6, 5, 4, 3, 2 };
    // std::array of size 8

// Sort items in aa
sort(aa.begin(), aa.end());

Null Pointer Keyword: nullptr

C++11 adds the keyword nullptr, representing an arbitrary null pointer.

[C++]

int * p = nullptr;

The older syntax for a null pointer was to use “0” in a pointer context.

[C++]

int * p = 0;  // Same as above

There was a standard macro NULL, but this was simply defined to be 0.

[C++]

int * p = NULL;  // Same as above

A problem with this syntax was that if 0 was used in a context where it could mean either an int or a pointer, then it would be treated as an int.

[C++]

void foo(int n)
{
    cout << "int" << endl;
}

void foo(int * p)
{
    cout << "pointer" << endl;
}

int main()
{
    foo(NULL);  // Before C++11, printed "int" (ICK!)
}

I recommend always using nullptr when a null pointer is intended.

Type Inference

C++11 adds features that allow a compiler to perform type inference: determining types without them being explicitly specified.

The first feature is a new use of the auto keyword. When a variable is declared, with a one-parameter constructor being called, replace the type with auto to make the type of the variable the same as the type of the constructor parameter.

[C++]

auto n = 3;    // n is an int, since 3 is an int
auto x = 3.2;  // x is a double, since 3.2 is a double

Be careful with strings! The type of a double-quoted literal is (const char *), not std::string.

[C++]

auto str = "abc";    // OOPS! str is (const char *)
cout << str.size();  // DOES NOT COMPILE

A solution to this was provided in C++14. Placing an s after a double-quoted literal converts it to a string object. This s is actually an operator in the Standard Library. It is declared in header <string>, and it is in namespace std::string_literals.

[C++14]

#include <string>
using namespace std::string_literals
auto str = "abc"s;  // str is a std::string

Note: The s suffix above is an example of a user-defined literal. These were actually introduced in C++11; however, the C++11 Standard Library does not define any examples of them. Several were added to the Standard Library in C++14: string-object literals, and various time-duration units. You can also define your own.

The second type-inference feature added to C++11 is the new keyword decltype. “decltype(EXPR)”, where EXPR is some expression, represents the type of the expression.

[C++]

decltype(a+b) x;  // x has same type as expression (a+b)

Range-Based For-Loops

C++11 introduces a new looping construct: the range-based for-loop. This can be used to iterate over any standard container.

[C++]

vector<int> v = { 10, 20, 30, 40 };
for (int i : v)
{
    cout << i << " ";
}
cout << endl;

Above, i goes through each of the values in the vector. So the output will be as follows.

10 20 30 40 

In practice, we almost never specify the type in a range-based for-loop; instead, we use the auto keyword.

[C++]

for (auto i : v)
{
    ...

Any of the standard parameter-passing methods are also available.

[C++]

// By reference
// Use when modifying items in the container.
for (auto & i : v)
{
    ...

// By reference-to-const
// Use when iterating through a container of objects
//  or items of unknown type.
for (const auto & i : v)
{
    ...

The range-based for-loop actually calls global functions begin and end on the given container. These generally return container.begin() and container.end(), respectively. If you are writing a class that does not have begin and end member functions, but you still want it to be usable with range-based for-loops, then you can define appropriate global functions begin and end.

Initializer lists have these defined, so we can do the following.

[C++]

for (auto i : { 10, 20, 30, 40 })
{
    ...

Compile-Time Execution: constexpr

C++11 introduces the new qualifier keyword constexpr, which marks values that are known at compile time.

[C++]

constexpr int n = 10;

The keyword can also qualify functions that can be executed at compile time.

[C++]

constexpr int sumSquares(int n)
{
    return n <= 0 ? 0 : sumSquares(n-1) + n*n;
}

When calling a constexpr function and storing its value, be sure to store it in a constexpr variable.

[C++]

constexpr int sumSquaresTo100 = sumSquares(100);

There are strong limitations on what code may be included in a constexpr function. Only constexpr functions may be called from a constexpr function. In C++11, the function body of a constexpr function may consist only of a single return statement. In C++14, this restriction was relaxed.

See fibo_slow_constexpr.cpp for example code using constexpr.