Modern C++

"Learning the fundamentals of a programming language is one thing; learning how to design and implement effective programs in that language is something else entirely. This is especially true of C++, a language boasting an uncommon range of power and expressiveness. Properly used, C++ can be a joy to work with. An enormous variety of designs can be directly expressed and efficiently implemented. A judiciously chosen and carefully crafted set of classes, functions, and templates can make application programming easy, intuitive, efficient, and nearly error-free. It isn't unduly difficult to write effective C++ programs, if you know how to do it. Used without discipline, however, C++ can lead to code that is incomprehensible, unmaintainable, inextensible, inefficient, and just plain wrong" - Scott Meyers, Effective C++, Third Edition

 

 

C++ is cross compiler, cross platform. Performance critical code is the bread and butter of C++. Conforming ISO C++.

 

Threads, atomics, futures, Lambda functions. C++ is very relevant for WinRT, GPGPU. C++ is the language for performance. C++M. 

 

Variadic Templates are very difficult to implement in the compiler. 

 

Writing code in C++ is clean and safe and fast.

 

clean: minimal lines of code or boilerplate. just like c#, javascript

 

safe: not fragile, no error codes, exception / type / memory safe

 

fast: c++

 

What has changed?

 

C++ is having a renaisance, because performance is back on the forefront. Excess power in CPU is now dropping off. We just have more cores. Kinect, Bigger experiences, data center (power distribution, ... safes a lot of money) efficiency. Mobile devices.

 

More and more people come back to c++

 

Core mindset:

Achieve C++’s value proposition of efficient abstraction.

Strong abstraction: Type-safe Object Oriented and generic code for modeling power, without sacrificing control and efficiency.

Full control over code and memory: You can always express what you want to do. nd you can always control memory and data layout exactly.

Pay-as-you-go efficiency: No mandatory overheads, don’t pay for what you don’t use. You can go in and tune the code, but only when necessary.

 

"The going word at Facebook is that <em>‘reasonably written C++ code just runs fast,’</em> which underscores the enormous effort spent at optimizing PHP and Java code. Paradoxically, C++ code is (may be) more difficult to write than in other languages, but efficient code is a lot easier." – Andrei Alexandrescu

 

Managed languages optimize for programmer productivity.

C++ optimizes for performance.

 

Use the right tool for the job.

 

old:

 

circle* p = new circle( 42 );

vector<shape*> vw = load_shapes();

for( vector<circle*>::iterator i = vw.begin(); i != vw.end(); ++i )

{

   if( *i && **i == *p )

       cout << **i << “ is a match\n”;

}

 

for( vector<circle*>::iterator i = vw.begin(); i != vw.end(); ++i )

{

   delete *i;

}

 

delete p;

 

 

Not even exception safe. Code should be at least twice as long. Missing try/catch, __try/__finally

 

new: 

 

auto p = make_shared<circle>( 42 );

vector<shared_ptr<shape>> vw = load_shapes();

for_each( begin(vw), end(vw), [&]( shared_ptr<circle>& s ) {

   if( s && *s == *p )    

cout << *s << “ is a match\n”;

});

 

auto: type inference, let the compiler infer the type for you. Like var in C#.

make_shared: prefer it over new. Use the standard reference counted smart pointer: shared pointers. No need to clean up the vector. No need for any deletes. Automatic lifetime management. Exception safe. T* => shared_ptr<T>, new -> make_shared.

 

for_each: 

STL algorithm. Loop body is a lambda function. [&]. Now calling STL library functions are now a lot more useful. They used to be too difficult to write.

 

DELETE.

Not necessary any longer. 

 

Stack lifetime.

Automatic Lifetime mangement: Efficient + Exception safe.

 

automatic variables: stack scope.

 

class widget {

private:

  gadget g;

public:

  void draw();

};

 

lifetime automatically tied to enclosing object. No leaks. Exception safe.

 

void f() {

  widget w;

  :::

  w.draw();

  :::

}

 

lifetime automatically tied to enclosing scope.

constructs w, including the w.g gadget member.

 

Automatic destruction and deallocation for w and w.g

Automatically exception safety, as if finally{ w.dispose(); w.g.dispose(); }

 

 

Heap Lifetime: standard smart pointer

 

class gadget;

 

class widget {

  private:

    shared_ptr<gadget> g;

};

 

shared ownership: still keeps gadget alive w/auto lifetime mgmt. No leak, exception safe.

 

class gadget {

  private:

    weak_ptr<widget> w;

}

 

use weak_ptr to break reference-count cycles.

 

Widget owns the gadget. Gadget doesn't own its widget.

 

unique_ptr<> : if you own it uniquely. Doesn't need a reference count. 

 

 

class node {

  vector<unique_ptr<node>> children;

  node* parent;

  :::

public:

  node(node *parent_)

    : parent(parent_)

  {

    children.push_back( new node(...) );

    :::

  }

};

 

node observes its parent. The raw pointer is perfectly fine.

plain "new" should immediately initialize another object that

owns it, usually a unique_ptr.

 

if/when performance optimization is needed, consider

well-encapsulated uses of owning *'s and delete (e.g. hidden inside objects).

 

Example: writing your own low-level data structure.

 

"C++ is the best language for garbage collection principally because it creates less garbage." --Bjarne Stroustrup

 

You don't create garbage.

 

 

Containers:

 

 

vector<string> v;

v.push_back("Geddy Lee");

 

vector: default container. Compact, efficient: cache-friendly, prefetcher-friendly. 

- Array that resizes itself. Heap-based, always owns its objects, always exception safe. Always efficient with 0-per-element overhead. It's compact. It's most efficient and cache-friendly when traversing the elements. There is nothing not to like about it. It's the default, but not the only thing. Use what is appropriate.

 

Size is fixed?

array<string, 50> a;

fixed size vector: array. compact, efficient: cache-friendly, prefetcher friendly. Safe to use, plays nice with STL algorithms. No heap allocation necessary.

 

Associate containers:

map (tree) or unordered_map (hash)

 

map<string, string> phone;

phone[“Alex Lifeson”] = “+1 (416) 555-1212”;

 

multimap<string, string> phone;

phone[“Neil Peart”] = “+1 (416) 555-1212”;

phone[“Neil Peart”] = “+1 (905) 555-1234”;

 

unordered_map<string, string> phone;

phone[“Alex Lifeson”] = “+1 (416) 555-1212”;

 

unordered_multimap<string, string> phone;

phone[“Neil Peart”] = “+1 (416) 555-1212”;

phone[“Neil Peart”] = “+1 (905) 555-1234”;

 

map: begin to end, stays sorted. Guaranteed. O(log(n)) insert and delete.

 

hash: Guaranteed O(constant) insert and delete.  

 

 

Loops:

 

old:

 

for (auto i= v.begin(); i!=v.end(); i++)

{

  ...

}

 

new:

 

for_each( begin(v), end(v), [](string& s) {

  ...

});

 

 

old:

 

auto i = v.begin();

for( ; i != v.end(); ++i) {

  if( *i > x && *i <y) break;

}

 

new: 

 

auto i = find_if( begin(v), end(v),

  [=](int i){ return i > x && i < y; }

);

 

always use non-member begin(), end().

 

 

Algorithms:

 

for_each: default traversal algorithm.

  also transform for not-in-place semantics.

 

find_if: the default search algorithm

 

default binary sorting and searching.

sort, lower_bound, et al.: 

 

use the same predicate for sort and search. Otherwise you get the wrong result.

 

use strict <, prefer named lambdas.

 

auto comp = [](const widget& w1, const widget& w2) {

  return w1.weight() < w2.weight();

};

 

sort( v.begin(), v.end(), comp );

 

auto i = lower_bound( v.begin(), v.end(), comp);

 

clean, fast, safe.

 

 

Value types & reference types

 

"Copyable" Value Types (Default)

 

class point {

  int x;

  int y;

  public:

 

   :::

   :::

};

 

Default in C++. All about memory and layout control. 

 

 

"Polymorphic" Reference Types

 

class shape: boost::noncopyable{

  public:

    virtual draw();

    :::

}

 

class circle: shape {

  public:

    virtual draw() override;

    :::

}

 

 

all about base classes and virtual functions.

 

explicitly disable copying

 

disabling the copying is inherited.

 

explicit override control.

 

 

 

 

Value types and Move efficiency:

 

set<widget> load_huge_data(){

  set<widget> ret;

  // ... load data and populate ret...

  return ret;

}

 

widgets = load_huge_data();

 

efficient, no deep copy.

no need for "heap allocation + return a pointer" workaround. 

 

Just steal it's gut. It is an "organ donor". That set of widgets is out of scope anyway. The return semantics is normally copy, but now you get move.

 

 

 

vector<string> v = IfIHadAMillionStrings();

 

v.insert( begin(v) + v.size()/2, "tom");

v.insert( begin(v) + v.size()/2, "richard");

v.insert( begin(v) + v.size()/2, "harry");

 

efficient, no deep copy-shuffle.

(just 1.5M ptr/len assignments). 

 

 

HugeMatrix operator+(

  const HugeMatrix&,

  const HugeMatrix& 

);

 

hm3 = hm1 + hm2;

 

efficient, no extra copies.

 

Move is an optimization of Copy for value types.

 

 

Enable Move For Appropriate Value Types

 

class my_class {

  unique_ptr<BigHugeData> data;

 

public:

  my_class( my_class&& other) // move construction

   : data( move (other.data)) {}

 

  my_class& operator=( my_class&& other )

  { data = move( other.data); } // move assignment

 

  :::

 

  void method() {

    if (!data) throw "moved-from object"; // check (if appropriate)

  }

 

}

 

 

Copy => Move: Also enable move

If it can be cheaper than a deep copy.

 

Move != Copy: Some non-value types are naturally move-only. Example: unique_ptr.