From C++03 to C++11

From Gridkaschool

Gridka School 2014 C++ course

  • Martin Heck, KIT EKP
  • Jörg Meyer, KIT SCC

Literature on C++

C++ books

  • The C++ Programming Language (4th Edition), Bjarne Stroustrup
  • Effective C++, Scott Meyers
  • More Effective C++: 35 New Ways to Improve Your Programs and Designs
  • Modern C++ Design, Andrei Alexandrescu
  • The C++ Standard Library, Nicolai M. Josuttis
  • C++ Templates, David Vanevoorde, Nicolai M. Josuttis
  • Exceptional C++, Herb Sutter
  • More Exceptional C++, Herb Sutter

C++11 books

  • The C++ Programming Language, 4th Edition, Bjarne Stroustrup
  • The C++ Standard Library: A Tutorial and Reference (2nd Edition), Nicolai M. Josuttis
  • C++11 programmieren, Torsten T. Will (German)
  • C++11: Der Leitfaden für Programmierer zum neuen Standard, R. Grimm (German)

C++ links

C++11 links

Technical aspects for the course

Gridka School slides

slides part 1

  • Bug avoidance: The C++11 Standard offers new ways in which the compiler can help you to avoid bugs. Especially the new keywords override, final, and smart pointers.

BugAvoidance.pdf

BugAvoidanceExamples.tgz

access to machines

You can run the exercises either on your own computer or ssh to one of our prepared machines using your Gridka School account.

  • connection from Linux/Unix: ssh -p24 <username>@gks-virt<xxx>.scc.kit.edu
  • connection from Windows: use an ssh client like putty, or use cygwin

This is a list of hostnames:
gks-virt084 gks-virt085 gks-virt088 gks-virt089 gks-virt098 gks-virt100 gks-virt101 gks-virt102

compilaton and execution of code

Most examples consist of just one cpp file, i.e. no header file, no additional library. Use the following commands to compile and run the code:

g++ mycode.cpp
./a.out

For C++11 support do:

g++ -std=c++11 mycode.cpp
./a.out

exercises

getting started with C++11

  • Simplify the following piece of code by new features of C++11 (auto, for-range, constructor delegation, uniform initialization)
#include<vector>
#include<string>


class user {
  std::string username;
  long uid;
  std::string encr_pwd;
  long get_next_uid() {
    return 42;
  }
public:
  user(std::string name, long id, std::string passwd) : username(name), uid(id), encr_pwd(passwd) {}
  user(std::string name, std::string passwd) : username(name), uid(0), encr_pwd(passwd) {
    uid=get_next_uid();
  }
  void reset_pwd() {encr_pwd="";}
};


int main() {

  std::vector<user> group1;
  group1.push_back(user("mueller",500,"$hd$nwcqw"));
  group1.push_back(user("meier",501,"$7rfd$skg"));
  group1.push_back(user("schmidt",502,"$46vxsk$"));

  for (std::vector<user>::iterator it=group1.begin();it!=group1.end();++it)
    it->reset_pwd();

}

constexpr

  • Write a function fac that calculates the factorial of a number at compile-time.
//...

int main() {
  for (auto i : {fac(15),fac(16),fac(17),fac(18),fac(19),fac(20)})
    std::cout<<i<<std::endl;
}

loops

  • Have a look at the following ways to loop over container elements. Which ones are new, i.e. only work in C++11? Add a range-based for loop.
#include<vector>
#include<iostream>
#include<iterator>     // std::ostream_iterator
#include<algorithm>    // std::for_each   

struct print_int {
  void operator() (int i) {
    std::cout<<i<<'\n';
  }
};

int main() {
   std::vector<int> v{1,4,2,3,4,5,7};

  for (int i=0,N=v.size();i!=N;++i)
    std::cout<<v[i]<<'\n';
  
  for (std::vector<int>::const_iterator it=v.begin();it!=v.end();++it)
    std::cout<<*it<<'\n';

  std::copy(v.begin(),v.end(),std::ostream_iterator<int>(std::cout,"\n"));

  print_int pi;
  std::for_each(v.begin(),v.end(),pi);

  for (auto it=v.cbegin();it!=v.cend();++it)
    std::cout<<*it<<'\n';         

  for (auto it=begin(v);it!=end(v);++it)
    std::cout<<*it<<'\n';         

  std::for_each(v.begin(),v.end(),[](int i){std::cout<<i<<'\n';});

}

lists

  • Compare sizeof(list<int>) and sizeof(forward_list<int>)

unordered_map

  • std::hash<std::string> provides a hash function for strings. Run the following code:
#include<iostream>
#include<unordered_map>
#include<string>

int main() {
  std::hash<std::string> h;
  std::cout<<h("Hello")<<" "<<h("World!")<<std::endl;
}
  • Write a simple class myClass with two strings as members and write a custom hash function for your class that calculates a hash from the hashes h1 and h2 of your member strings (e.g. h1 ^ ( h2 << 1 );).
class hash<myClass> {
 public:
    size_t operator()(const S &s) const; 
};
  • Create an unordered_map with myClass as keys and int as value.

smart pointer

  • Add a memory management to the binary search tree by introducing adequate smart pointers.
  • In which order do the nodes get deleted?

binary_tree.hpp

#ifndef BINARY_TREE_HPP
#define BINARY_TREE_HPP

#include<initializer_list>

class binary_tree {
  struct node {
    node(int k): key(k), left(0), \
         right(0), p(0) {}
    int key;
    node *left, *right, *p;
  };

public:
  node* root;
  void insert(node* z);
  void inorder_print(node* x);
  binary_tree(std::initializer_list<int> values): root(0) {
    for (auto it=values.begin(); \
      it!=values.end();++it) 
	insert(new node(*it));
  }
};

#endif

binary_tree.cpp

#define BINARY_TREE_CPP
#include<iostream>
#include "binary_tree.hpp"

void binary_tree::insert(node* z) {
    node* y=0;
    node* x=root;
    while (x) {
      y=x;
      if (z->key < x->key) 
	x=x->left;
      else
	x=x->right;    
    }
    z->p = y;
    if (!y)
      root=z; // tree was empty
    else if (z->key < y->key)
      y->left = z;
    else
      y->right = z;
}

void binary_tree::inorder_print(node* x) {
    if (x) {
      inorder_print(x->left);
      std::cout<<x->key<<" ";
      inorder_print(x->right);
    }
}


int main() {
  binary_tree bt{12,5,5,7,2,4};
  bt.inorder_print(bt.root);//2 4 5 5 7 12  
}

override

  • You can add override behind the declaration of a function.
  • Compile, then add override and recompile, see the difference?

INIOverride.cc

#include <iostream>

/** Struct to hold information about cars. 
 *
 *  To have multiple cars of a certain different type, you can inherit from this struct.
 */
struct Car {
    /** Returns a generic estimate of the weight of a car (1 ton), if not overriden.*/
    virtual int getWeightInKg();

    /** Returns a generic estimate of the speed of a car (180), if not overriden.*/
    virtual int getMaxSpeedInKmh() const;
    
    /** Returns a generic estimate of the number of wheels of a car (4), if not overriden.*/
    virtual int getNWheels();
    
    /** Returns a generic estiate of the number of seats in a car (5), if not overrriden.*/
    int getNSeats();
};

/** Struct to use with the bigger and slower Monster Truck spcial car.*/
struct MonsterTruck: public Car {
  /** Monster Trucks are much heavier on average the averge cars (3 tons).*/
  int getWeightInKg();

  /** Monster Trucks are much slower than average cars (80).*/
  int getMaxSpeedInKmh();
    
  /** Monster Trucks have double wheels (--> 8) due to their big weight.*/
  virtual int getNWheels();
  
  /** Monster Trucks have one big front bench with 3 seats.*/
  int getNSeats();
};

int main()
{
  using namespace std;

  MonsterTruck monsterTruck;
  Car& car = monsterTruck;
  cout << "Weight:           " << car.getWeightInKg() << endl;
  cout << "Speed:            " << car.getMaxSpeedInKmh() << endl;
  cout << "Number of Wheels: " << car.getNWheels() << endl;
  cout << "Number of Seats:  " << car.getNSeats() << endl;

  cout << "Once more the Speed: "           << monsterTruck.getMaxSpeedInKmh() << endl;
  cout << "Once more the number of Seats: " << monsterTruck.getNSeats() << endl;
}

//---------------------------------------------------------------------------------

int Car::getWeightInKg(){
  return 1000;
}

int MonsterTruck::getWeightInKg(){
  return 3000;
}

int Car::getMaxSpeedInKmh() const {
  return 180;
}

int MonsterTruck::getMaxSpeedInKmh() {
  return 80;
}

int Car::getNWheels() {
  return 4;
}

int MonsterTruck::getNWheels() {
  return 8;
}

int Car::getNSeats(){
  return 5;
}

int MonsterTruck::getNSeats(){
  return 3;
}

final

  • Introduce final where it conceptionally belongs and recompile.

INIFinal.cc

#include <iostream>

struct Vehicle {
  enum class VehicleType{
    HooverVehicle, // = 0
    GroundVehicle  // = 1
  };

  virtual VehicleType getVehicleType() const = 0;
};

struct Car: public Vehicle {
  VehicleType getVehicleType() const override {
    return VehicleType::GroundVehicle;
  };
};

struct MonsterTruck: public Car {
  VehicleType getVehicleType() const override  {
    return VehicleType::HooverVehicle;
  };
};

int main()
{
  using namespace std;

  MonsterTruck monsterTruck;
  Car& car = monsterTruck;
  cout << "Vehicle Type: " << static_cast<int>(car.getVehicleType()) << endl;
}

comments to exercises

smart pointers

  • The obvious solution uses shared pointers. That said, using them everywhere will result in circular dependencies, preventing correct deallocation of resources. The easiest way of addressing the problem is to use a weak_ptr for the parent - we never actually dereference parents in the code so we needn't worry about implementing the taking of temporary ownership.

binary_tree.hpp:

#ifndef BINARY_TREE_HPP
#define BINARY_TREE_HPP

#include <initializer_list>
#include <memory>


class binary_tree {

  struct node {
    node(int k) :
        key(k)
    {}
    ~node();

    int key;
    std::shared_ptr<node> left, right;
    std::weak_ptr<node> p;
  };

 public:
  std::shared_ptr<node> root;
  void insert(std::shared_ptr<node> z);
  void inorder_print(std::shared_ptr<node> x);
  binary_tree(std::initializer_list<int> values)
  {
    root.reset();
    for (auto it=values.begin(); it!=values.end();++it)
      insert(std::shared_ptr<node>(new node(*it)));
  }
};

#endif

binary_tree.cpp:

#include<iostream>
#include "binary_tree.hpp"


binary_tree::node::~node()
{
  std::cout << "Deleting node with key=" << key << std::endl;
}


void binary_tree::insert(std::shared_ptr<node> z) {
  std::shared_ptr<node> x = root;
  std::shared_ptr<node> y;
  while (x) {
    y = x;
    if (z->key < x->key)
      x = x->left;
    else
      x = x->right;
  }
  z->p = y;
  if (!y)
    root = z; // tree was empty
  else if (z->key < y->key)
    y->left = z;
  else
    y->right = z;
}


void binary_tree::inorder_print(std::shared_ptr<node> x) {
  if (x) {
    inorder_print(x->left);
    std::cout << x->key << " ";
    inorder_print(x->right);
  }
}


int main() {
  binary_tree bt{12, 5, 5, 7, 2, 4};
  bt.inorder_print(bt.root);//2 4 5 5 7 12
  std::cout << std::endl;
}


  • It is also possible to use unique_ptr for left and right, and to use raw pointers (node*) for the parents p:
#include<initializer_list>
#include<iostream>
#include <memory>


class binary_tree {

  struct node {
    node(int k): key(k), left(nullptr), right(nullptr), p(0) {}
    int key;
    std::unique_ptr<node> left;
    std::unique_ptr<node> right;
    node* p;
    void print()  {
      std::cout<<"key: "<<key<<" left: "<<(left?left->key:0)<<" right: "<<(right?right->key:0)<<" p: "<<(p?p->key:0)<<std::endl;
    }
  };
public:
  node* root;
  void insert(node* z) {
    node* y=0;
    node* x=root;
    while (x) {
      y=x;
      if (z->key < x->key) 
	x=x->left.get();
      else
	x=x->right.get();    
    }
    z->p = y;
    if (!y)
      root=z; // tree was empty
    else if (z->key < y->key)
      y->left.reset(z);
    else
      y->right.reset(z);
  }

  binary_tree(std::initializer_list<int> values): root(0) {
    for (auto it=values.begin();it!=values.end();++it) {
      insert(new node(*it));
    }
  }

  void inorder_print(node* x) {
    if (x) {
      //x->print();
      inorder_print(x->left);
      std::cout<<x->key<<" ";
      inorder_print(x->right);
    }
  }


  void inorder_print(std::unique_ptr<node> const& x) {
    inorder_print(x.get()); 
  }


};

int main() {
  binary_tree bt{12,5,5,7,2,4};
  bt.inorder_print(bt.root);
  std::cout<<std::endl;
}