The

Updating

With

C++11

Richard Bateman

r@zzt.net

OpenWest 2015

Presentation Goal:

Discuss new C++11 features

 

This presentation is not exhaustive!

Type safety with

nullptr

// Old way
MyClass* classPtr = NULL;
// New way
MyClass* classPtr = nullptr;

What's the difference?

nullptr is not an int!

bool isCorrect(int a) {
    // Return true if a is between -20 and +20 inclusive
    return a >= -20 || a <= 20;
}

bool isCorrect(const char* myStr) {
    // Return true if the pointer is non-null
    return !myStr;
}
isCorrect(15);

true

isCorrect(-25);

false

isCorrect(NULL);

true

isCorrect(nullptr);

false

char* e = NULL;
isCorrect(e);

false

using a better typedef

using:a better typedef

// Create a type with typedef
typedef std::map<std::string, int> IntMap;

// Create the same thing with using
using IntMap = std::map<std::string, int>;

Basic example:

Cute... but why??

using is more versatile

// With typedef

template <typename T>
struct StringMap {
    typedef std::map<std::string, T> type;
};
StringMap<int>::type mytype;

Making a string-indexed map type:

// With using
template <typename T>
using StringType = std::map<std::string, T>;
StringMap<int> mytype;

rvalue references and move semantics

std::string lvalString = "Stuff";

callFunction("Stuff");
callFunction(lvalString);

lvalString = std::string("More stuff!");

lvalues

rvalues

Can you identify all of the lvalues and rvalues?

rvalue references

#include <iostream>

using std::cout; using std::endl; using std::string;

void printString(string& myString) {
    cout << "lRef string: " << myString << endl;
}
void printString(string&& myString) {
    cout << "rRef string: " << myString << endl;
}

int main() {
    printString("Test string");
    printString(std::string("another test string"));
    std::string lvStr("lval String");
    printString(lvStr);

    return 0;
}
$ g++ -std=c++11 -o test testFile.cpp ; ./test
rRef string: Test string
rRef string: another test string
lRef string: lval String

What will the output be?

move semantics

std::move turns a lvalue into a rvalue

#include <utility>

move constructors and move assignments use rvalue references

A move constructor or move assignment moves the memory from the old object to the new object, invalidating the old

std::string a("foo");
std::string b = std::move(a);
// a will be invalid! Don't do this! =]

Type inference with

auto

auto a = 5;            // int
auto b = 3.5;          // double
auto c = 3.5f;         // float
auto d = "Something";  // const char*

Cute... but why??

Consider:

template <typename T>
int theyShouldPayMeMore(
        const std::map< std::string, std::pair<int, T>> inMap ) {
    for (std::map< std::string, std::pair<int, T>>::const_iterator
            it = inMap.begin();
            it != inMap.end(); ++it) {
        std::pair<int, T> curPair = inMap->second;
        callFn(pair.second);
    }
}

Now with auto

template <typename T>
int theyShouldPayMeMore(
        const std::map< std::string, std::pair<int, T>> inMap ) {
    for (auto it = inMap.begin(); it != inMap.end(); ++it) {
        auto curPair = inMap->second;
        callFn(pair.second);
    }
}

Range-based for loops

Combine with range loop:

template <typename T>
int theyShouldPayMeMore(
        const std::map< std::string, std::pair<int, T>> inMap ) {
    for (auto cur : inMap) {
        auto curPair = inMap.second;
        callFn(pair.second);
    }
}

And just to clean it up, add using

template <typename T>
using IntPairMap<T> = std::map< std::string, std::pair<int, T> >;

template <typename T>
int theyShouldPayMeMore(const IntPairMap<T> inMap ) {
    for (auto cur : inMap) {
        auto curPair = inMap.second;
        callFn(pair.second);
    }
}

range loops and vectors

for (auto cur : intVector) {
    callFn(cur);
}

Type inference with

decltype

// Make an int
auto a = 5;

// Make another int! (and a compiler warning)
decltype(a) b = 3.5;

Cute... but why??

Consider:

vector<string> a{"3", ".", "1", "4"};

auto a = 0;               // a is an int
auto b = a.size();        // b is size_t
decltype(a.size()) c = 0; // c is size_t

for (decltype(a.size()) i = 0; i < a.size(); ++i) {
    // If I'd used auto it wouldn't be the same type!
}

Difference from auto:

const int a = 42;    // const int
auto b = a;          // int (drops const)
decltype(a) c = a;   // const int

Get your Fu on with

lambda expressions, std::bind and std::function

Lambda expressions

The output of a lambda expression is a special unnamed temporary ClosureType object

WAT?

Lambda expressions

Rules for lambdas:

  1. Don't worry about type, just accept (and use auto or std::function)
  2. Be very aware of scope
  3. Be very aware of lifetime
  4. Bask in the wonderfulness that is C++ closures

Lambda expressions

  1. Don't worry about type, just accept (and use auto or std::function)
#include <algorithm>
int countMatches(const vector<int>& haystack, int needle) {
    auto countFilter = [needle](int cur) -> bool {
        return cur == needle;
    };
    return std::count_if(haystack.begin(),
                         haystack.end(),
                         countFilter);
}

Lambda expressions

  1. Be very aware of scope
auto closure = [](arg1, arg2, ...) -> retType {
    // Function body
};

[ ] stores the Capture List:

  • [ ] captures nothing from the parent scope
  • [=] automatically captures used variables by value
  • [&] automatically captures used variables by ref
  • [this] captures this pointer by value
  • [a,b] captures a and b by value
  • [&a,b] captures a by ref, b by value

Lambda expressions

std::function<int(int)> getAdder(int lh) {
    // Return a function that adds lh to its argument
    return [&lh](int rh) { return lh+rh; };
}

auto fn = getAdd(5);
cout "Added value: " << fn(3) << endl;
  1. Be very aware of lifetime

FAIL

Never capture by reference a variable that may not be around when you call the closure!

std::bind

returns a callable object of unspecified type which wraps an existing callable type

// Needed for _1, _2, _3, etc
using namespace std::placeholders;

std::vector<int> intVec;
auto addToVec = std::bind(&(std::vector<int>::push_back), &intVec, _1);

addToVec(3);
addToVec(4);

#include <functional>

Functor (noun):

An instance of a C++ class with the operator() overloaded so that it can be called like a function

std::function is a templated functor class which wraps any callable target -- functions pointers, lambda expressions, or other functors.

#include <functional>

using namespace std::placeholders; // for _1, _2, _3, ...
using MessageHandler = std::function<bool(int,std::string)>;

bool any(vector<MessageHandler> fnList, int p1, std::string p2) {
    auto result = false;
    for (auto fn : fnList) {
        result |= fn(p1, p2);
    }
    return result;
}
bool numIsPositive(int p1, std::string p2) {
    return p1 > 0;
}
auto evenSize = [](int p1, std::string p2) -> bool {
    return (p1 + p2.size()) % 2;
};
MessageHandler numIsPositiveAlt
    = std::bind(numIsPositive, _1, _2);

vector<MessageHandler> handlers
     = {&numIsPositive, evenSize, numIsPositiveAlt};
cout << std::boolalpha << any(handlers, 0, "") << endl;
cout << std::boolalpha << any(handlers, 1, "") << endl;
cout << std::boolalpha << any(handlers, -1, "") << endl;

Uniform Initializers

// Creates a two element array of std::string objects
string a[] = { "awesome", "presentation" };

// Creates a string "great presenter"
string b("great presenter");

// Creates ints
int c = 5;
int d(5);

Initializing old-style:

// Method 1
vector<string> a;
a.push_back("awesome");
a.push_back("presentation");

// Method 2
vector<string> a(2)
a[0] = "awesome";
a[1] = "presentation";

// With boost list_of
vector<string> a = boost::assign::list_of("awesome")("presentation");

Initializing a vector of strings:

YUCK!

Uniform Initializers

// Creates a two element array of std::string objects
string a[]{ "awesome", "presentation" };

// Creates a string "great presenter"
string b{"great presenter"};

// Creates an int
int c{5};

Initializing new-style:

// Vector
vector<string> a{ "awesome", "presentation" };

// Map
map<string, int> b{ { "Presenter", "Richard" }, { "Class", "Awesome" } };

Initializing STL containers:

SWEET!

Uniform Initializers

Caveat

// Creates a vector of size 2 with both values empty
vector<string> a(2);

// Creates a vector of size 1 with value 2
vector<string> a{2};

C++11 smart pointers

Why not use raw pointers?

3 new pointer types:

  • std::unique_ptr
  • std::shared_ptr
  • std::weak_ptr

C++11 smart pointers

std::unique_ptr

#include <memory>

std::unique_ptr<MyObject> makeObject() {
    std::unique_ptr<MyObject> objPtr = new MyObject();

    objPtr->WriteMyPresentationForMe();
    objPtr->DoOtherStuff();
    objPtr->FinishGettingReady();
    return objPtr;
}

{
    auto ptr = makeObject();
    ptr->PresentStuff();
}

C++11 smart pointers

std::shared_ptr

#include <memory>

auto a = std::make_shared<MyObject>(1, 2, 3);
// a is type std::shared_ptr<MyObject>

auto b = a;
auto c = b;
a.reset();
b.reset();
a=c;
b=a;

a.reset();
b.reset();
c.reset(); // destructor is called here

C++11 smart pointers

std::weak_ptr

#include <memory>

void testPtr(std::weak_ptr<MyObject> ptr) {
    if (auto tmp = ptr.lock()) {
        cout << "Pointer is good";
    } else {
        cout << "Pointer expired";
    }
}

auto a = std::make_shared<MyObject>(1, 2, 3);
// a is type std::shared_ptr<MyObject>

auto b = std::weak_ptr<MyObject>(a);
testPtr(c); // Pointer is good
testPtr(b); // Pointer is good
testPtr(a); // Pointer is good
a.reset();    
testPtr(b); // Pointer expired
testPtr(a); // Pointer expired

There is more....

We don't have time to list all changes

Here are a few more quick ones

Strongly-typed ENUM

// Traditional enum
enum Color {RED, GREEN, ORANGE, YELLOW};
enum Fruit {GRAPE, ORANGE, APPLE, PEAR};
// This won't compile! ORANGE conflicts

// enum classes
enum class Color {RED, GREEN, ORANGE, YELLOW};
enum class Fruit {GRAPE, ORANGE, APPLE, PEAR};

// strongly-typed enum
enum class Errors : uint32_t { BROKEN, UGLY, SAD };

// To use enum classes:

Color a = Color::RED;
Fruit b = Fruit::ORANGE;

Explicitly Defaulted and Deleted Functions

Applies to:

  • Copy constructor
  • Copy assignment operator
  • Move constructor
  • Move assignment operator

Explicitly Defaulted and Deleted Functions

struct noncopyable
{
  noncopyable() {};
  
private:
  noncopyable(const noncopyable&);
  noncopyable& operator=(const noncopyable&);
};

The old way:

Explicitly Defaulted and Deleted Functions

struct noncopyable
{
  noncopyable() =default;
  noncopyable(const noncopyable&) =delete;
  noncopyable& operator=(const noncopyable&) =delete;
};

The new way:

= delete explicitly deletes the function

= default explicitly uses the default generated function

Delegating constructors

class Foo {
public:    
   Foo() : Foo(0) {}
   Foo(int i) : Foo(i, 0) {}
   Foo(int i, int j) : num1(i), num2(j) {
      average = (num1+num2) / 2;
   }
private:
   int num1;
   int num2;
   int average;
};

That's all, folks!

Made with Slides.com