Martin Sulzmann
Memory managment and copy/move semantics
Lambdas and local type inference
Overloading
Templates
Heap allocated data under user control via new and
delete.
Too many deletes => crash!
Too few deletes => memory leak!
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!
}Resource Acquisition Is Initialization (RAII)
Allocate via constructor
Deallocate via destructor
Copy/move semantics
Copy (duplicate) content of object to avoid that two variables refer to the same memory location (inefficient)
Move content, efficient, but after move shall no longer access content
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!
}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 }
Capture list specifies non-local variables used in function body (more on this below)
List of parameters
Return type ( can be inferred, see examples below)
Function type
auto x = 1;
xstd::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.
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.
C++ supports overloading of functions and operators.
Overloading is restricted to argument types.
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”.
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;
}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;
}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);
}We use here overloading in combination with templates.
The “instance” for vectors is missing a type bound!!!
We only consider type-specific test data generation.
We make use of lambdas to emulate lazy evaluation of generators.
We make use of template specialization to implement “arbitrary”.
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>.
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 *));qsort is “generic” thanks to “void*”
But not type safe!
Our goal:
Type safe version of qsort
Don’t reimplement qsort
Rather build a “typing layer” around qsort to guarantee type safety
// 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>);
}// 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();
}