The C++ programming language

Martin Sulzmann

The C++ programming language

Memory managment and copy/move semantics

Heap allocated data under user control via new and delete.

Tricky bits

void deleteIssues() {

  string* p = new string("Hello");

  cout << *p;

  string* q = p;

  delete p;

  cout << *q;   // Access to deleted object!

  delete q;     // Deleting twice yields run-time error!

}

“Smart” pointers

Resource Acquisition Is Initialization (RAII)

Copy/move semantics

void testString() {
  shared_ptr<string> p = make_shared<string>("Hello");

  cout << *p;

  shared_ptr<string> q = p;

  cout << *q;

}


void testString2() {
  shared_ptr<string> p = make_shared<string>("Hello");

  cout << *p;

  shared_ptr<string> q = move(p);

  cout << *q;

  cout << *p;  // After a move can no longer access the object, cause it has been moved!

}

Lambdas and local type inference

“Add”

int myAdd(int x, int y) {
  return x+y;
}

void testAdd() {
  cout << "\n" << myAdd(1,3);

  auto myAdd2 = [](int x, int y) -> int { return x+y; };

  cout << "\n" << myAdd2(1,3);

}

C++ lambda notation:

[capture](parameters) -> return_type { function_body }

auto x = 1;

“Plus”

std::function<int(int)> myPlus(int x) {
  return [x](int y) -> int { return x+y; };
}

void testPlus() {
  cout << "\n" << myPlus(1)(3);

  auto inc = myPlus(1);

  cout << "\n" << inc(3);

  auto myPlus2 = [](int x) -> std::function<int(int)> { return [x](int y) -> int { return x+y; }; };

  auto myPlus3 = [](int x)  { return [x](int y)  { return x+y; }; };

  cout << "\n" << myPlus2(1)(3);
  cout << "\n" << myPlus3(1)(3);
}

Partial function application, see inc(3).

Inference of return types, see myPlus3.

Capture by copy versus capture by reference

void testLambdaCapture() {
  auto x = 1;

  auto f_copy = [x] (int y) -> int { return x+y; };
  auto f_ref = [&x] (int y) -> int { return x+y; };

  cout << "\n" << f_copy(3);
  cout << "\n" << f_ref(3);
  x = 2;
  cout << "\n" << f_copy(3);
  cout << "\n" << f_ref(3);
}

Variable x is used in the lambda function f.

Must specify if we wish capture by copy or by reference.

Capture by copy written [x]. “Easier” for compiler, no need to track non-local variables.

Capture by reference written [&x]. Each access to x will retrieve its current value.

Overloading

C++ supports overloading of functions and operators.

Overloading is restricted to argument types.

Safe array access

class MyString {
  string s;
public:
  MyString(string _s) { s = _s; }
  char operator[](int i) {
    if (i <= 0 && i < s.length()) {
      return s[i];
    }
    cout << "out of bounds";
    return '\0';
  }
};

void testAccess() {
  MyString s("Hello");

  cout << s[0];

  cout << s[10];
}

BTW, strings are already “safe”.

Showing values of different type

string Show(string s) { return s; }

string Show(bool b) {
  return b ? "true" : "false";
}

void testShow() {
  string s = "X";
  cout << Show(s) << Show(true);
}

Templates

Simple example

template<typename T>
void mySwap(T& x, T&y) {
  T tmp;

  tmp = x; x = y; y = tmp;
}

void testSwap() {
  int x = 1; int y = 3;
  cout << "\n" << x << y;
  mySwap(x,y);
  cout << "\n" << x << y;
}

Monomorphisation

Templates are “not real”.

Templates will be instantiated. That is, we create a copy of the code for each specific instance. This process is called monomorphisation.

The Template-free code will then be processed by the compiler.

void mySwap_int(int& x, int&y) {
  int tmp;

  tmp = x; x = y; y = tmp;
}

void testSwap_int() {
  int x = 1; int y = 3;
  cout << "\n" << x << y;
  mySwap_int(x,y);
  cout << "\n" << x << y;
}

“Show” with Templates

string Show(bool x) {
  return x ? "true" : "false";
}

// Missing type bound, we require Show(T).
template<typename T>
string Show(vector<T> xs) {
  string s;

  s = "[";

  for(int i=0; i < xs.size(); i++) {
    s = s + Show(xs[i]);
    if (i != xs.size() - 1) {
      s = s + ",";
    }
  }
  s = s + "]";

  return s;
}

void testShow() {
  vector<bool> xs;

  xs.push_back(true);
  xs.push_back(false);

  cout << Show(xs);
}

QuickCheck again

We only consider type-specific test data generation.

Recall that arbitrary is a function overloaded on the return type. However, C++ overloading is more restricted. Overloading must be resolved based on the argument types only. Hence, we resort to template specialization.

// Generator interface.
// The actual generator is a parameter-less function.
// We "force" generation by calling this function.
// In essence, we emulate here "lazy" evaluation.
template<typename T>
class Gen {
 public:
  Gen() {};
  Gen(std::function<T()> gen_) { gen = std::function<T()>(gen_); }
  std::function<T()> gen;
  T generate() { return gen(); };
};


// Computing an arbitrary generator.
// Uses template specialization.
template<typename T>
Gen<T> arbitrary();

// Some instances

// bool
template<>
Gen<bool> arbitrary() { return Gen<bool>([] () -> bool { return (rand() % 2 == 0) ? true : false; }); }

// char
template<>
Gen<char> arbitrary() { return Gen<char>([] () -> char { return (char)(rand() % 255); }); }

// vector<char>
template<>
Gen<vector<char>> arbitrary() { return Gen<vector<char>>([] () -> vector<char> {
      auto n = rand() % 11;     // max of 10 characters
      vector<char> vs;
      auto ch = arbitrary<char>();

      for(int i=0; i < n; i++) {
    vs.push_back(ch.generate());
      }
      return vs; });
}


void testGen() {
  auto g = arbitrary<vector<char>>();

  vector<char> vs = g.generate();
  string s(vs.begin(), vs.end());

  cout << "\n" << s;
}

Point to note. It seems impossible to a “generic” arbitrary instance for vector<T>.

Type-safe qsort

We make use here of lambdas and variable templates (requires C++14)

Recall the prototype of qsort.

void qsort(void *base,
           size_t num_elements ,
           size_t element_size,
           int (*compare)(void const *,
                          void  const *));

Our goal:

// Variable templates C++14
// Needs to be initialized with an value, to be overwritten later.
// The rhs value needs to be generic.
template<typename T>
int (*cmpFunc)(T,T) = &f;

template<typename T>
int cmpHelper(const void* x, const void* y) {
  return (*cmpFunc<T>)(*((T*)x), *((T*)y));
}


template<typename T>
void mysort(T* base, size_t len, int cmp(T,T)) {


  // qsort expects a function pointer.
  // We assign the type-specific cmp function to a global variable.
  // The helper cmp function will then access this cmp function
  // via the global variable.

  cmpFunc<T> = cmp;
  qsort(base, len, sizeof(T), &cmpHelper<T>);
}

Complete source code

// Usage: g++ --std=c++14

#include <functional>
#include <iostream>
#include <string>
#include <stdlib.h>
#include <stdio.h>
#include <vector>
#include <cstdlib>
#include <memory>
using namespace std;


//////////////////////////////////////////////////
// Memory managment and copy/move semantics

void deleteIssues() {

  string* p = new string("Hello");

  cout << *p;

  string* q = p;

  delete p;

  //  cout << *q;   // Access to deleted object!

  //  delete q;     // Deleting twice yields run-time error!

}

void testString() {
  shared_ptr<string> p = make_shared<string>("Hello");

  cout << *p;

  shared_ptr<string> q = p;

  cout << *q;

}


void testString2() {
  shared_ptr<string> p = make_shared<string>("Hello");

  cout << *p;

  shared_ptr<string> q = move(p);

  cout << *q;

  //  cout << *p;  // After a move can no longer access the object, cause it has been moved!

}



//////////////////////////////////////////////////
// Lambdas and local type inference (auto)

// Lambdas in C++
int myAdd(int x, int y) {
  return x+y;
}

void testAdd() {
  cout << "\n" << myAdd(1,3);

  auto myAdd2 = [](int x, int y) -> int { return x+y; };

  cout << "\n" << myAdd2(1,3);

}

// More lambdas.
std::function<int(int)> myPlus(int x) {
  return [x](int y) -> int { return x+y; };
}

void testPlus() {
  cout << "\n" << myPlus(1)(3);

  auto inc = myPlus(1);

  cout << "\n" << inc(3);

  auto myPlus2 = [](int x) -> std::function<int(int)> { return [x](int y) -> int { return x+y; }; };

  auto myPlus3 = [](int x)  { return [x](int y)  { return x+y; }; };

  cout << "\n" << myPlus2(1)(3);
  cout << "\n" << myPlus3(1)(3);
}


// Capture by copy versus capture by reference
void testLambdaCapture() {
  auto x = 1;

  auto f_copy = [x] (int y) -> int { return x+y; };
  auto f_ref = [&x] (int y) -> int { return x+y; };

  cout << "\n" << f_copy(3);
  cout << "\n" << f_ref(3);
  x = 2;
  cout << "\n" << f_copy(3);
  cout << "\n" << f_ref(3);
}


//////////////////////////////////////////////////
// Overloading


class MyString {
  string s;
public:
  MyString(string _s) { s = _s; }
  char operator[](int i) {
    if (i <= 0 && i < s.length()) {
      return s[i];
    }
    cout << "out of bounds";
    return '\0';
  }
};

void testAccess() {
  MyString s("Hello");

  cout << s[0];

  cout << s[10];
}


string Show(string s) { return s; }

string Show(bool b) {
  return b ? "true" : "false";
}

void testShow() {
  string s = "X";
  cout << Show(s) << Show(true);
  //  cout << Show("X") << Show(true);
  // yields truetrue why!?
}


////////////////////////////////////
// Templates

template<typename T>
void mySwap(T& x, T&y) {
  T tmp;

  tmp = x; x = y; y = tmp;
}

void testSwap() {
  int x = 1; int y = 3;
  cout << "\n" << x << y;
  mySwap<int>(x,y);
  cout << "\n" << x << y;
}

// C++ applies "monomorphisation"
void mySwap_int(int& x, int&y) {
  int tmp;

  tmp = x; x = y; y = tmp;
}


void testSwap_int() {
  int x = 1; int y = 3;
  cout << "\n" << x << y;
  mySwap_int(x,y);
  cout << "\n" << x << y;
}

// Show

/*
string Show(bool x) {
  return x ? "true" : "false";
}
*/

// Missing type bound, we require Show(T).
template<typename T>
string Show(vector<T> xs) {
  string s;

  s = "[";

  for(int i=0; i < xs.size(); i++) {
    s = s + Show(xs[i]);
    if (i != xs.size() - 1) {
      s = s + ",";
    }
  }
  s = s + "]";

  return s;
}

void testShow2() {
  vector<bool> xs;

  xs.push_back(true);
  xs.push_back(false);

  cout << Show(xs);
}


// "arbitrary" see QuickCheck


// Generator interface.
// The actual generator is a parameter-less function.
// We "force" generation by calling this function.
// In essence, we emulate here "lazy" evluation.
template<typename T>
class Gen {
 public:
  Gen() {};
  Gen(std::function<T()> gen_) { gen = std::function<T()>(gen_); }
  std::function<T()> gen;
  T generate() { return gen(); };
};


// Computing an arbitrary generator.
// Uses template specialization.
template<typename T>
Gen<T> arbitrary();

// Some instances

// bool
template<>
Gen<bool> arbitrary() { return Gen<bool>([] () -> bool { return (rand() % 2 == 0) ? true : false; }); }

// char
template<>
Gen<char> arbitrary() { return Gen<char>([] () -> char { return (char)(rand() % 255); }); }

// vector<char>
template<>
Gen<vector<char>> arbitrary() { return Gen<vector<char>>([] () -> vector<char> {
      auto n = rand() % 11;    // max of 10 characters
      vector<char> vs;
      auto ch = arbitrary<char>();

      for(int i=0; i < n; i++) {
    vs.push_back(ch.generate());
      }
      return vs; });
}


void testGen() {
  auto g = arbitrary<vector<char>>();

  vector<char> vs = g.generate();
  string s(vs.begin(), vs.end());

  cout << "\n" << s;
}


////////////////////////////////////////////////////////////////////
// Type safe qsort, playing with lambdas and templates



/* Recall the prototype of qsort.

void qsort(void *base,
           size_t num_elements ,
           size_t element_size,
           int (*compare)(void const *,
                          void  const *));

*/


// qsort is "generic" thanks to "void*"
// But not type safe!


template<typename T>
void printArray(T* x, int len, void pr(T)) {
  for(int i=0; i<len; i++) {
    pr(x[i]);
  }
}

int cmpInt(const void* x, const void* y) { return *((int*)x) >= *((int*)y); }

int cmpFloat(const void* x, const void* y) { return *((float*)x) >= *((float*)y); }

void testQsort() {

  int x[3] = {3,2,1};
  printArray<int>(x, 3, [] (int x) { printf("%d", x); });
  printf("\n");
  qsort(x, 3, sizeof(int), &cmpInt);
  printArray<int>(x, 3, [] (int x) { printf("%d", x); });

  float y[2] = {1.0, 2.0};
  qsort(y, 2, sizeof(float), &cmpInt); // Sollte sein &compFloat !!!

}


template<typename T>
int f(T x, T y) { return 1; }

// Typ-safe version of qsort.
// Requires.


// Variable templates C++14
// Needs to be initialized with an value, to be overwritten later.
// The rhs value needs to be generic.
template<typename T>
int (*cmpFunc)(T,T) = &f;

template<typename T>
int cmpHelper(const void* x, const void* y) {
  return (*cmpFunc<T>)(*((T*)x), *((T*)y));
}


template<typename T>
void mysort(T* base, size_t len, int cmp(T,T)) {


  // qsort expects a function pointer.
  // We assign the type-specific cmp function to a global variable.
  // The helper cmp function will then access this cmp function
  // via the global variable.

  cmpFunc<T> = cmp;
  qsort(base, len, sizeof(T), &cmpHelper<T>);
}


void testQsortSafe() {

  int x[3] = {3,2,1};
  printArray<int>(x, 3, [] (int x) { printf("%d", x); });
  printf("\n");
  mysort<int>(x, 3, [](int x, int y) -> int { return x > y; });
  printArray<int>(x, 3, [] (int x) { printf("%d", x); });


  printf("\n");

  float y[3] = { 1.0, 2.0, 3.0 };
  printArray<float>(y, 3, [] (float x) { printf("%f", x); });
  mysort<float>(y, 3, [](float x, float y) -> int { return x < y; });
  printf("\n");
  printArray<float>(y, 3, [] (float x) { printf("%f", x); });
}


//////////////////////////////////////////
// Main

int main() {
  deleteIssues();
  testString();
  testString2();
  testAdd();
  testPlus();
  testLambdaCapture();
  testAccess();
  testShow();
  testSwap();
  testShow2();
  testGen();
  testQsort();
  testQsortSafe();
}