530 likes | 625 Views
Writing Modern C++. Marc Grégoire Software Architect marc.gregoire@nuonsoft.com http://www.nuonsoft.com / http://www.nuonsoft.com/blog /. Agenda. Why C++? C++ Core Value Modern C++ Resources. Why C++?. C++ is having a kind of renaissance People are coming back to C++
E N D
Writing Modern C++ Marc Grégoire Software Architect marc.gregoire@nuonsoft.com http://www.nuonsoft.com/ http://www.nuonsoft.com/blog/
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 safe • Leak 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 • Often you can use std::shared_ptr, even for the above example Deterministic
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:void qsort(void *base, size_tnum, size_tsize,int (*comparator) (constvoid *, const void *)); // Call it as follows for a double arrayqsort(myDoubleArray, n, sizeof(double), compare_double); Number of bytes in one element Number of elements in memory Memory to be sorted Comparison function to compare two elements
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: howtocapture variables fromenclosing scope • parameters(optional): parameter list, justlike a function • mutable(optional): variables capturedby-value are const, mutablemakesthem non-const • exception_specification(optional): = throw list • return_type(optional): the return type; ifomitted • If the body of the lambda expression is of the following form:{ return expression; }the type of expression will become the return_typeof the lambda expression. • Otherwise the return_type is void
Lambda Expressions • Basic example: int main() { []{cout << "Hello from Lambda" << endl;}(); } • Capture block • [ ]capturesnothing • [=] 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++ T* shared_ptr<T> new make_shared auto type deduction Old circle* p = newcircle(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; 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"; } ); for/while/do std:: algorithms[&] lambda functions no need for “delete” automatic lifetime management exception-safe not exception-safe missing try/catch, __try/__finally
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[] 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(){MyObjectobj; // ...}
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 lifetime automatically tied to enclosing scope constructs w, including the w.g gadget member void f() { widget w; /* ... */ w.draw(); /* ... */ } class widget { private: gadget g; public: void draw();}; Automatic destruction and deallocation for w and w.g lifetime automatically tied to enclosing object no leak, exception safe 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 unique ownership node ownsits children no leak, exception safe class node { vector<unique_ptr<node>> children;node* parent;/* … */ public: node( node* parent_) : parent(parent_) {children.push_back( new node(…) ); /* … */ }}; 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.”— BjarneStroustrup
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: cache-friendly, prefetcher-friendly set: Like map, but only keys • Avoid using C-style arrays • Instead use modern constructs such as STL containers • std::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 fixed size vector 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 for/while/do std:: algorithms[&] lambda functions arbitrary length lambda bodies, just put the loop body inside the lambda Old for (auto i = v.begin(); i!= v.end(); ++i) { /* ... */} auto i = v.begin();for (; i != v.end(); ++i) { if (*i > x && *i < y) break;} New for_each(begin(v), end(v), [](string& s) { /* ... */ } ); auto i = find_if(begin(v), end(v), [=](int i) { return i > x && i < y; } ); for_eachto visit each element find_ifto find a match 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() for not-in-place semantics • find_if(): Your default search algorithm • sort(), 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 • 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(), … • Numerical processing algorithms • count(), count_if(), accumulate(), inner_product(), … • Comparison algorithms • equal(), mismatch(), … • Operational algorithms • for_each()
Algorithms • Nonmodifying algorithms examples • // Find min and max with 1 algorithmauto minmax = minmax_element(begin(vec), end(vec));cout << "Min = " << *(minmax.first) << " and Max = " << *(minmax.second) << endl; • // Find the first subsequence of two consecutive 8sauto 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), [](inti){return i == 1;});
Algorithms • Numerical processing algorithms examples • // Calculate arithmetic mean of the elements in a vectordouble sum = accumulate(begin(vec), end(vec), 0);double avg = sum / vec.size(); • // Calculate geometric mean of the elements in a vectordouble 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 14vector<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 vectortransform(begin(vec), end(vec), begin(vec), [](inti){return i + 100;}); • // Replace all values < 0 with 0replace_if(begin(vec), end(vec), [](inti){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 elementsstrings.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? 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"); HugeMatrix operator+(constHugeMatrix&,constHugeMatrix&); hm3 = hm1+hm2; set<widget>load_huge_data() { set<widget> ret; // … load data and populate ret … return ret;} widgets = load_huge_data(); efficient, no deep copy-shuffle (just 1.5M ptr/len assignments) efficient, no deep copy no need for “heap allocation + return a pointer” workaround efficient, no extra copies
Move Semantics – How To Implement? 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"; }}; • Needs: • Move constructor • Move assignment operator 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