Lambdas & Miscellany

These notes are a collections of miscellaneous topics, mostly new to C++11 version, not all of which are explicitly covered in the course.

The 2011 update of the C++ language standard introduced a whole bunch of new features; we’ve used some, but also we’ve been doing things the “old fashioned” way, for two reasons. First, C++ has been around a long time and there’s a lot of legacy code out there that you’ll need to understand. And second, some of the features can be confusing, or can hide and obscure what’s really going on, if you don’t already understand what they’re shorthand for.

We’ll give a brief overview of some of these features here; we won’t have time to go into depth on everything, so for a more detailed and comprehensive look, check out Stroustrup’s C++11 FAQ.

lambdas (covered)

Other Features

In no particular order…

auto

Examples:

int a = 7; // a is an int
auto b = 7; // b is also an int
auto c = 7.0; // c is a double
std::vector<std::string> v; // v is a vector of strings
auto iter = v.begin(); // iter is a std::vector<std::string>::iterator

template <class T, class U>
void printMul(const T &left, const U &right) {
    auto result = left * right; 
    // result is whatever type we get when we apply the multiplication 
    // operator to a T and a U
    std::cout << result;
}

Type aliasing (aka template alias)

As an alternative to typedef, C++11 gives us another way to specify a name for a type:

using sviter = std::vector<std::string>::iterator;

The advantage of this over typedef is that it plays nice with templates, so we can have some template arguments specified, and some blank, to do something like:

template<class T>
using Vec = std::vector<T,My_alloc<T>>; // always use my custom allocator

Vec<int> v = {1, 2, 3, 4, 5}; // type of v is std::vector<int, My_alloc<int>>

nullptr

We’ve used this one; if you’re not using -std=c++11, it won’t work. In that case, you can still use the old C-style NULL macro. The main difference is that NULL is literally a macro for 0, whereas nullptr actually has a pointer type (i.e. it’s not an integer). This means the compiler can do proper typechecking: e.g.

int *ip = NULL; // fine, i is a null pointer
int i = NULL; // fine, i is 0
int *jp = nullptr; // fine, j is a null pointer
int j = nullptr; // error; won't compile due to incompatible types

range-for

As a shortcut for writing a for-loop using an iterator, we can write a for-all loop similar to the way we can in Java or Python. The following two loops both print out all the elements of a vector:

std::vector<int> vec = {1, 2, 3, 4, 5};

for (auto x : vec) { // type of x will be int
  std::cout << x;
}

// is the same as

for (auto itr = vec.begin(); itr != vec.end(); itr++) { // type of itr will be an iterator
  std::cout << *itr;
}

Note that the range-for loop gives us copies of the elements, rather than an iterator; we don’t need to de-reference, and we can’t increment/decrement. If we want to change the values of the elements, we need to use references. For example, to add one to each element of the vector, we could do:

for (auto &x : vec) { // type of x will be a reference to an int
  x++;
}
// vec is now {2, 3, 4, 5, 6}

"Raw" string literal

std::string s1 = "word\\t\\\\\\t";
std::string s2 = R"(word\t\\\t)"; // s1 and s2 contain the same characters

delegating constructors

In C++11, one constructor for a class is allowed to call another constructor for that class; in earlier versions of the language, this was not allowed. So, for instance you can now have a default constructor that works by calling another with a default value, e.g.:

Class C{
  private:
    int data; // should be in range 0-10

  public:
    C(int x) {
      // squash x to be in the range 0-10
      x = x <0 ? 0 : x;
      x = x >10 ? 10 : x;
      data = x;
    }

    C() : C(7) {} // default to a value of 7
};

override and final

class A {
  public:
    virtual void f(int) const;
    virtual int g(double);
    virtual int h() const final;
};

class B : public A {
  public:
    void f(int) override; // compiler will give us an error because we forgot const
    int g(double) override; // okay, no problems
    int h() const; // error, can't override because it was declared final
};

Initializer lists

Prior to C++11, there were different ways of initializing variables depending on their type.

int x = 7;
int y[4] = { 1, 2, 3, 4};
std::vector<int> v(4, 1); //make a vector with 4 ints, each with value 1

In C++11, we can now use the curly-brace notation safely, regardless of the type of the thing we’re initializing. The following is equivalent to the previous example:

int x{7};
int y[4] {1, 2, 3, 4};
std::vector<int> v{1, 1, 1, 1};

You can also use empty braces to say you want a default initializer, which works even for basic types:

int a{}; // a is 0
double b{}; // b is 0.0
int *p{}; // p is nullptr
char buf[12]{}; // buf contains 12 null characters (i.e. 12 copies of '\0')
std::string s{}; // equivalent to std::string s, calls default constructor for class

Class default initializers

class C {
  private:
    int x;
  public:
    C(): x(7) {} // default constructor initializes x to 7
};

class C {
  private:
    int x = 7;
  public:
    C() {} // x is automatically initialized to 7
};

default and delete

C++11 introduces the keywords default and delete as a way of controlling which of the “free” class methods you actually want to use. Normally, you get a default constructor, as well as (shallow) copy constructor and operator methods. You can use =default to explicitly declare that you’re using the default versions, or =delete to explicitly prevent the default versions from being used. E.g.:

class D {
  public:
    D() = default; // automatic default constructor
    D(const D &) = delete; // no automatic copy constructor
};

move vs. copy, rvalues

Sometimes, making copies of things is wasteful; e.g. copying a temporary value that’s about to go away may be very expensive; it would be better to just “steal” the original version of that object for the assignment. E.g.:

std::string s1 = "this is a long string";
std::string s2 = f(s1);
class E {
  // ...
  E(const E&); // copy constructor
  E(E&&); // move constructor
  E& E=(const E&); // copy assignment
  E& E=(E&&); // move assignment
};

Remember swap? This makes it a lot more efficient! But we may need to tell the compiler it’s safe to treat things as r-values, which we do with a special function called move():

template<class T>
void swap(T &a, T &b) {
  T tmp = move(a); // a is probably broken after this
  a = move(b); // b is probably broken after this, but a is fixed
  b = move(tmp); // tmp is probably broken after this, but b is fixed
}

Note that this swap method will work for any class that has a move-assignment operator defined; if we don’t define move-assign, this won’t work! Fortunately for you, the STL has been re-written so that it all takes advantge of move semantics, meaning that all sorts of stuff got more efficient overnight without even needing any changes to user code.

static_assert

struct S { X m1; Y m2; };
static_assert(sizeof(S)==sizeof(X)+sizeof(Y),"unexpected padding in S");

noexcept

int func(int a, int b) noexcept;