Presentation: “Writing Modern C++”

advertisement
Writing Modern C++
Marc Grégoire
Software Architect
marc.gregoire@nuonsoft.com
http://www.nuonsoft.com/
http://www.nuonsoft.com/blog/
April 3rd 2012
Agenda




Why C++?
C++ Core Value
Modern C++
Resources
Why C++?




C++ is having a kind of renaissance
People are coming back to C++
Main reason: performance / €
You want to use the hardware as efficient as possible
and squeeze the most out of it
 Mobile
devices: have limited power, use it efficiently
 Datacenters: reducing power requirements directly results
in saving money
C++ Core Value

Efficient abstraction
Strong abstraction: Type-safe OO and templates for powerful
modeling, without sacrificing control and efficiency
 Full control over code execution and memory:

you can always express what you want to do
 you can always control memory and data layout exactly, if you want to


Pay-as-you go efficiency
no mandatory overheads, don’t pay for what you don’t use
 Example: just because C++ supports virtual functions, you don’t pay a
penalty for their support if you don’t use them

C++ Core Value


Cross platform, cross compiler, cross operating system
Performance very important
“It’s incredibly important for C++ to be the language of
performance. If there is a language lower than C++ and that has
more performance, we didn’t do our job as a C++ community.” –
Herb Sutter
C++ Core Value
“The going word at Facebook is that ‘reasonably written C++
code just runs fast,’ which underscores the enormous effort spent
at optimizing PHP and Java code.
Paradoxically, C++ code is more difficult to write than in other
languages, but efficient code is a lot easier.”
– Andrei Alexandrescu
Modern C++











Clean, Safe, Fast
Things to unlearn
Lambda expressions
Old C++ versus new C++
Avoid delete
Automatic lifetime (stack & heap)
Containers
Loops
Algorithms
Move semantics
Compile time encapsulation
Clean, Safe, Fast

Modern C++ code can be Clean, Safe, and Fast
 Clean:
express with minimal lines of code what you want to
do, just as in other modern languages, resulting in easy to
read and easy to understand code
 Safe: modern C++ code is exception safe, memory safe, …
 Fast: because it’s C++
Modern C++











Clean, Safe, Fast
Things to unlearn
Lambda expressions
Old C++ versus new C++
Avoid delete
Automatic lifetime (stack & heap)
Containers
Loops
Algorithms
Move semantics
Compile time encapsulation
Things to Unlearn



If you have used C++ before, you might have to
unlearn a couple of things
Avoid low-level pointer manipulation and raw memory
manipulation, in favor of higher level constructs
Do not use delete / delete [], use smart pointer; it’s:
 Exceptions
 Leak
safe
free
 Deterministic, unlike garbage collectors
Things to Unlearn

Never do something like:
FILE* f = fopen("data.ext", "w");
// ...
fclose(f);


Not exception safe!
Use RAII (Resource Acquisition Is Initialization)

Write a wrapper class:
Constructor  opens the file
 Destructor  automatically closes the file
Deterministic 


Often you can use std::shared_ptr, even for the above example
Things to Unlearn

Instead of:
FILE* f = fopen("data.ext", "w");
// ...
fclose(f);

Use:
shared_ptr<FILE> filePtr(fopen("data.ext", "w"), fclose);

Or write your own wrapper
Things to Unlearn

Avoid the old C-style algorithms, instead, use modern
C++ algorithms
Things to Unlearn

For example, qsort() is a C-style algorithm with
following signature:
Memory to be sorted
Number of elements
in memory
Number of bytes in
one element
void qsort (void *base, size_t num, size_t size,
int (*comparator) (const void *, const void *));
Comparison function to
compare two elements
// Call it as follows for a double array
qsort(myDoubleArray, n, sizeof(double), compare_double);
Things to Unlearn
Side-note:
Even though std::sort() is a
higher level construct, it’s
faster than qsort by a large
factor (not just a few
percent)
Use C++ algorithms like std::sort()
Example:
std::sort(begin(vec), end(vec));
Modern C++











Clean, Safe, Fast
Things to unlearn
Lambda expressions
Old C++ versus new C++
Avoid delete
Automatic lifetime (stack & heap)
Containers
Loops
Algorithms
Move semantics
Compile time encapsulation
Lambda Expressions

Syntax
[capture_block](parameters) mutable exception_specification ->
return_type { body }

capture block: how to capture variables from enclosing scope

parameters (optional): parameter list, just like a function

mutable (optional): variables captured by-value are const, mutable makes them non-const

exception_specification (optional): = throw list

return_type (optional): the return type; if omitted

If the body of the lambda expression is of the following form:
{ return expression; }
the type of expression will become the return_type of the lambda expression.

Otherwise the return_type is void
Lambda Expressions

Basic example:
int main()
{
[]{cout << "Hello from Lambda" << endl;}();
}

Capture block







[ ] captures nothing
[=] captures all variables by value
[&] captures all variables by reference
[&x] captures only x by reference and nothing else
[x] captures only x by value and nothing else
[=, &x, &y] captures by value by default, except variables x and y, which are captured by
reference
[&, x] captures by reference by default, except variable x, which is captured by value
Modern C++











Clean, Safe, Fast
Things to unlearn
Lambda expressions
Old C++ versus new C++
Avoid delete
Automatic lifetime (stack & heap)
Containers
Loops
Algorithms
Move semantics
Compile time encapsulation
Old C++ Versus New C++
auto type deduction
Old
New
T*  shared_ptr<T>
new  make_shared
circle* p = new circle(42);
vector<shape*> vw = load_shapes();
auto p = make_shared<circle>(42);
for (vector<circle*>::iterator i =
vector<shared_ptr<shape>> vw =
vw.begin(); i != vw.end(); ++i)
load_shapes();
{
for_each (begin(vw), end(vw),
if (*i && **i == *p)
[&](shared_ptr<circle>& s)
cout << **i << " is a match\n";
{
}
if (s && *s == *p)
for (vector<circle*>::iterator i =
cout << *s << " is a match\n";
vw.begin(); i != vw.end(); ++i)
}
{
);
delete *i;
for/while/do 
not exception-safe
no need for “delete”
}
std:: algorithms
missing try/catch,
automatic lifetime management
delete p;
__try/__finally
exception-safe
[&] lambda functions
Modern C++











Clean, Safe, Fast
Things to unlearn
Lambda expressions
Old C++ versus new C++
Avoid delete
Automatic lifetime (stack & heap)
Containers
Loops
Algorithms
Move semantics
Compile time encapsulation
Avoid Delete

Write your code in such a way that there is never a
need to use delete or delete[]
Avoid Delete

Don’t write code as follows:
void foo()
{
MyObject* p = new MyObject();
// ...
delete p;
}
 It’s
not exception safe!
Avoid Delete

Instead use shared_ptr or unique_ptr:
void foo()
{
unique_ptr<MyObject> p = new MyObject();
// ...
}

Or, even better, just do:
void foo()
{
MyObject obj;
// ...
}
Modern C++











Clean, Safe, Fast
Things to unlearn
Lambda expressions
Old C++ versus new C++
Avoid delete
Automatic lifetime (stack & heap)
Containers
Loops
Algorithms
Move semantics
Compile time encapsulation
Automatic Lifetime

Automatic Lifetime = Efficient + Exception Safe
class widget {
private:
gadget g;
public:
void draw();
};
lifetime automatically
tied to enclosing object
no leak, 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
Automatic exception safety, as if
“finally { w.g.dispose();
w.dispose(); }”
The Heap and Smart Pointers
class gadget;
class widget {
private:
shared_ptr<gadget> g;
};
class gadget {
private:
weak_ptr<widget> w;
};
shared ownership
keeps gadget alive
w/auto lifetime mgmt
no leak, exception safe
use weak_ptr to break
reference-count cycles
Side-note:
Never use the old
auto_ptr, it’s officially
deprecated!
The Heap and Smart Pointers
class node {
vector<unique_ptr<node>> children;
node* parent;
/* … */
public:
node( node* parent_)
: parent(parent_)
{
children.push_back( new node(…) );
/* … */
}
};
unique ownership
node owns its children
no leak, exception safe
node observes its parent
plain “new” should immediately
initialize another object that owns
it, example unique_ptr or
shared_ptr
C++ and Garbage Collection
“C++ is the best language for garbage collection
principally because it creates less garbage.”
— Bjarne Stroustrup
Modern C++











Clean, Safe, Fast
Things to unlearn
Lambda expressions
Old C++ versus new C++
Avoid delete
Automatic lifetime (stack & heap)
Containers
Loops
Algorithms
Move semantics
Compile time encapsulation
Containers


Your default container: vector
compact, efficient: cachefriendly, prefetcher-friendly
set: Like map,
but only keys
Avoid using C-style arrays
Instead use modern constructs such as STL containers
std::vector
fixed size vector
 std::array
 std::map
 std::unordered_map
 std::multimap
 std::unordered_multimap
 std::set

std::unordered_set
 std::multiset
 std::unordered_multiset
 std::list
 std::forward_list

Key-value-pairs: map (tree)
or unordered_map (hash)
Containers - Examples
vector<string> v;
v.push_back("Geddy Lee");
array<int, 50> a;
a[0] = 123;
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";
Modern C++











Clean, Safe, Fast
Things to unlearn
Lambda expressions
Old C++ versus new C++
Avoid delete
Automatic lifetime (stack & heap)
Containers
Loops
Algorithms
Move semantics
Compile time encapsulation
Loops arbitrary length lambda
Old
bodies, just put the loop
body inside the lambda
New
for/while/do 
std:: algorithms
[&] lambda functions
for (auto i = v.begin();
i != v.end(); ++i)
{
/* ... */
}
for_each (begin(v), end(v),
[](string& s)
{
/* ... */ for_each to visit each element
}
);
find_if to find a match
auto i = v.begin();
for (; i != v.end(); ++i)
{
if (*i > x && *i < y)
break;
}
auto i = find_if(begin(v), end(v),
[=](int i)
{
return i > x && i < y;
}
prefer non-member
);
begin()/end()
Modern C++











Clean, Safe, Fast
Things to unlearn
Lambda expressions
Old C++ versus new C++
Avoid delete
Automatic lifetime (stack & heap)
Containers
Loops
Algorithms
Move semantics
Compile time encapsulation
Algorithms


Don’t write your own algorithms unless you have a
good reason or there is no standard algorithm for your
use-case
Prefer standard algorithms
 for_each():
Your default traversal algorithm
 transform()
 find_if():
 sort(),
for not-in-place semantics
Your default search algorithm
lower_bound(), …: Your default sorting and searching
Algorithms

Utility Algorithms
 min(),
max(), minmax(), swap()
 minmax() example (C++11)
int x1 = 2, x2 = 9, x3 = 3, x4 = 12;
pair<int,int> p1 = minmax({x1,x2,x3,x4});
cout << "Minmax of 4 elements is "
<< p1.first << "," << p1.second << endl;
Algorithms

Nonmodifying algorithms

Search algorithms


Numerical processing algorithms


count(), count_if(), accumulate(), inner_product(), …
Comparison algorithms


find(), find_if(), find_if_not(), find_first_of(), search_n(), lower_bound(),
upper_bound(), equal_range(), minmax_element(), all_of(), any_of(),
none_of(), …
equal(), mismatch(), …
Operational algorithms

for_each()
Algorithms

Nonmodifying algorithms examples

// Find min and max with 1 algorithm
auto minmax = minmax_element(begin(vec), end(vec));
cout << "Min = " << *(minmax.first) << " and Max = "
<< *(minmax.second) << endl;

// Find the first subsequence of two consecutive 8s
auto it = search_n(begin(vec), end(vec), 2, 8);

// all_of()
vector<int> vec = {1,1,1,1};
bool b = all_of(begin(vec), end(vec),
[](int i){return i == 1;});
Algorithms

Numerical processing algorithms examples

// Calculate arithmetic mean of the elements in a vector
double sum = accumulate(begin(vec), end(vec), 0);
double avg = sum / vec.size();

// Calculate geometric mean of the elements in a vector
double mult = accumulate(begin(vec), end(vec), 1,
[](int num1, int num2){return num1 * num2;});
double geomean = pow(mult, 1.0 / vec.size());

// Create a vector with values 5 6 7 8 9 10 11 12 13 14
vector<int> vec(10);
iota(begin(vec), end(vec), 5);
Algorithms

Modifying algorithms
 transform(),
copy(), copy_if(), move(), swap_ranges(),
replace(), replace_if(), fill(), generate(), remove(),
remove_if(), reverse(), rotate(), next_permutation(), …
Algorithms

Modifying algorithms examples

// Add 100 to each element in the vector
transform(begin(vec), end(vec), begin(vec),
[](int i){return i + 100;});

// Replace all values < 0 with 0
replace_if(begin(vec), end(vec),
[](int i){return i < 0;}, 0);

// Remove all empty strings from a vector of strings
// (Use remove-erase pattern!)
auto it = remove_if(begin(strings), end(strings),
[](const string& str){return str.empty();});
// erase the removed elements
strings.erase(it, strings.end());
Algorithms

Sorting algorithms
 sort(),

stable_sort(), partial_sort(), merge(), …
Set algorithms
 set_union(),
set_intersection(), set_difference(),
set_symmetric_difference(), …
Algorithms

Sorting algorithms example
If you want to do some binary search (lower_bound,
upper_bound, equal_range, …), the sequence should be sorted
first
 Be sure to sort the sequence with the same predicate as you
give to the search algorithm
 Use named lambda, example:

auto comp = [](const widget& w1, const widget& w2)
{ return w1.weight() < w2.weight(); }
sort(begin(v), end(v), comp);
auto i = lower_bound(begin(v), end(v), w, comp);
Modern C++











Clean, Safe, Fast
Things to unlearn
Lambda expressions
Old C++ versus new C++
Avoid delete
Automatic lifetime (stack & heap)
Containers
Loops
Algorithms
Move semantics
Compile time encapsulation
Move Semantics


C++11 Move Semantics increases efficiency and results
in cleaner, easier to understand code
Why is move semantics useful?
Move Semantics – Why useful?
set<widget> load_huge_data() {
set<widget> ret;
// … load data and populate ret …
return ret;
}
vector<string> v = AMillionStrings();
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)
widgets = load_huge_data();
efficient, no deep copy
no need for “heap allocation
+ return a pointer”
workaround
HugeMatrix operator+(
const HugeMatrix&,
const HugeMatrix&
);
hm3 = hm1+hm2;
efficient, no extra copies
Move Semantics – How To Implement?

Needs:


Move constructor
Move assignment operator
class my_class
{
unique_ptr<BigHugeData> data;
public:
/* ... */
my_class(my_class&& other)
: data(move(other.data))
{ }
my_class& operator=(my_class&& other)
{
data = move(other.data);
}
void method()
{
if (!data) throw "moved-from object";
}
check (if appropriate)
};
Move Semantics – When?


If you have a copy constructor or copy assignment
operator: Also implement move versions if they can be
cheaper than a deep copy
Some types have only move versions, and no copy
versions
 For
example: some types are naturally move-only, such as
unique_ptr
Modern C++











Clean, Safe, Fast
Things to unlearn
Lambda expressions
Old C++ versus new C++
Avoid delete
Automatic lifetime (stack & heap)
Containers
Loops
Algorithms
Move semantics
Compile time encapsulation
Compile Time Encapsulation?

Use the Pimpl idiom to truly hide private members
class my_class {
// ... all public and protected stuff goes here ...
private:
class impl;
unique_ptr<impl> pimpl;
// opaque type here
};
my_class.h
class my_class::impl {
// defined privately here
// ... all private data and functions: all of these
//
can now change without recompiling callers ...
};
my_class::my_class() : pimpl(new impl)
my_class.cpp
{
/* ... set impl values ... */
}


Avoids rebuild cascades
Most appropriate for types used often
Resources



“Writing modern C++ code: how C++ has evolved over
the years” – Herb Sutter

http://channel9.msdn.com/Events/BUILD/BUILD2011/TOOL-835T

http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/KeynoteBjarne-Stroustrup-Cpp11-Style
“GoingNative 2012, Keynote: C++ Style” – Bjarne
Stroustrup
Presentations from GoingNative 2012
http://channel9.msdn.com/Events/GoingNative/GoingNative-2012
nd Edition
 Professional C++, 2
 http://www.wiley.com/WileyCDA/WileyTitle/productCd-0470932449.html

Questions
?
I would like to thank Herb Sutter from Microsoft
for his permission to base this presentation on one
that he wrote for Build 2011.
Download