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.