Modern C++: A Look into Language Constructs

how to train your modern c n.w
1 / 61
Embed
Share

Dive into the world of modern C++ with this guided tour through language constructs, starting from its inception to the latest features introduced in C++11. Join Thierry Lavoie on a journey exploring classes, inheritance, polymorphism, templates, memory models, RAII, and more. Discover why C++ matters, especially for performance-critical industries like video games and data structures.

  • C++
  • modern C++
  • guided tour
  • language constructs
  • Thierry Lavoie

Uploaded on | 0 Views


Download Presentation

Please find below an Image/Link to download the presentation.

The content on the website is provided AS IS for your information and personal use only. It may not be sold, licensed, or shared on other websites without obtaining consent from the author. If you encounter any issues during the download, it is possible that the publisher has removed the file from their server.

You are allowed to download the files provided on this website for personal or commercial use, subject to the condition that they are used lawfully. All files are the property of their respective owners.

The content on the website is provided AS IS for your information and personal use only. It may not be sold, licensed, or shared on other websites without obtaining consent from the author.

E N D

Presentation Transcript


  1. How To Train Your Modern C++ (A Guided Tour In The Zoo Of Wild Language Constructs)

  2. Prologue (What did I get myself into?)

  3. Name: Thierry Lavoie Current occupation: manager, static analysis technologies at Synopsys Credentials: Principal voter for Synopsys on WG21, PhD in static analysis

  4. First started writing C++ in 2000 Standard C++ was new and shiny! It was the emerging dominant language in the industry

  5. Why do I care about C++? I come from the video game industry, then the world of data structures Performance matters!

  6. Why this talk? Because I deeply care about C++ Because C++ is in a deep need of care!

  7. C++98 and C++03

  8. C++ was born in 1985 (me too!) First standardized as ISO/IEC 14882:1998, amended in 2003 as ISO/IEC 14882:2003 References in this talk are from the latest working draft: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4700.pdf

  9. Classes Inheritance Polymorphism Templates Memory model and RAII Destructors Exceptions Overloading That s how it all started!

  10. C++11

  11. R-value reference Strongly-typed enums Non-static data member initializers Forward declaration of enums Variadic templates Standardized attribute syntax Initializer lists Generalized constant expressions Static assertions Alignment support Auto-typed variables Conditionally-support behavior Lambda expressions Delegating constructors Declared type of an expression Inheriting constructors Right angle brackets Explicit conversion operators Default template arguments for function templates New character types Solving the SFINAE problem for expressions Unicode character names in literals Alias templates Raw string literals Extern templates Standard layout types Null pointer constant Defaulted functions Extended friend declarations Deleted functions Extending sizeof Explicit virtual overrides Inline namespaces Defining move special member functions Unrestricted unions Range-based for

  12. Static assertions Runtime assertions (from C): assert(integral_expression); C++11 has compile time assertions: static_assert(bool_constexpr, compile time error message! );

  13. Static assertions #include <type_traits> const int i = 10; void f() { static_assert(i == 11, "global i is not equal to 10"); } struct A { virtual ~A(); }; struct B : public A { static_assert(!std::is_polymorphic<A>::value, "A shouldn't be polymorphic"); }; $ g++ -std=gnu++11 assertions.cpp assertions.cpp:In function void f() : assertions.cpp:6:3: error: static assertion failed: global i is not equal to 10 static_assert(i == 11, "global i is not equal to 10"); assertions.cpp: At global scope: assertions.cpp:14:3: error: static assertion failed: A shouldn't be polymorphic static_assert(!std::is_polymorphic<A>::value, "A shouldn't be polymorphic");

  14. Auto-typed variables Variable declarations are allowed to use auto as the type of a variable. For example: auto pi = 3.1416; The type of the declared variable is deduced from the initializer expression according to the template argument deduction rules. In the previous example, the deduced type of the variable pi is double. Note that changing the literal in the initializer to the following: auto pi = 3.1416f; changes the type of pi to float.

  15. Auto-typed variables The previous remark highlights a fundamental difference between explicit typing and auto-typing: with auto-typing, one must be careful about the exact type of the initializer expression. For example: float pi = 3.1416; declares a variable pi of type float, whereas auto pi = 3.1416; declares a variable pi of type double.

  16. Auto-typed variables Let s consider the following: A buzz(); auto var = buzz(); A* foo(); auto ptr = foo(); const A& bar(); auto ref = bar(); What s the type of each variable in the previous example? Are every examples valid?

  17. Auto-typed variables One of the type should be somewhat surprising A buzz(); auto var = buzz(); // var is of type A A* foo(); auto ptr = foo(); // ptr is of type A* const A& bar(); auto ref = bar(); // ref is of type A (!?) Remember what we said about the type deduction rules?

  18. Auto-typed variables The idea behind type deduction is to consider the following invented function: template <class U> void f(U u); // Adapted from clause 10.7.4.1, par.5 and to deduce the type U as if the following was called: auto var = <expression>; f(<expression>);

  19. Auto-typed variables struct A {}; // Bonus question: what is the size of A? template<class U> void f(U u) { static_assert(std::is_same<U,A>::value, "U is not A"); // uses #include <type_traits> static_assert(std::is_same<U,const A&>::value, "U is not const A&"); } const A& bar() { const static A a; return a; } int main() { auto ref = bar(); f(ref); return 0; } $ g++ -std=gnu++11 t.cpp t.cpp: In instantiation of void f(U) [with U = A] : t.cpp:15:8: required from here t.cpp:8:3: error: static assertion failed: U is not const A& static_assert(std::is_same<U,const A&>::value, "U is not const A&");

  20. Auto-typed variables Going back to our example: const A& bar(); auto ref = bar(); // ref is of type A Using our synthetic example, we have shown that A is the expected type if we follow the rules (precisely 17.9.2.1, par. 2.3). How to use auto and still get a variable of type const A&? By qualifying auto! const A& bar(); const auto& ref = bar(); // ref is of type const A& Remember this: it will be important in a moment!

  21. R-value reference Before looking at this feature, let s make a bold claim: this is the most important feature introduced in C++ since 1998! To understand the claim, consider the following: template<class T> void swap(T& a, T& b) { T tmp(a); a = b; b = tmp; } What is the main problem with this code? (Hint: it could do much more than what it claims to be doing)

  22. R-value reference What we really want is the following: template<class T> void swap(T& a, T& b) { T tmp(std::move(a)); a = std::move(b); b = std::move(tmp); } But std::move doesn t exist in C++03 because we can only express value-semantics! (To see why, think for a second about destructing objects)

  23. R-value reference Let s consider a more complex problem: vector<unsigned int> get_long_vector(unsigned int n) { vector<unsigned int> quad; quad.reserve(n); // Always reserve your vectors (when you can)! (It runs in 75s without reserve) quad.push_back(0); for(unsigned int i = 0; i < n; ++i) { quad.push_back(i + quad.back()); } return quad; } int main(int argc, char** argv) { const unsigned int SIZE = 100000000; vector<vector<unsigned int> > matrix; matrix.reserve(SIZE); for(unsigned int i = 0; i < SIZE; ++i) { matrix.push_back(get_long_vector(10)); } cout << matrix.back().back() << endl; return 0; } Compiled with C++03, this code runs in 43s. Compiled with C++11, this code runs in 31s; this is 39% faster! Why? Because C++03 has to copy the return value of get_long_vector!

  24. R-value reference C++11 introduced the notion of r-value reference. An r-value reference is declared with &&: void handle_move(A&& a); Binding a temporary value to an r-value reference will extend its lifetime. This was possible in C++03 by binding a value to a const l-value reference (const A&), but the value was not modifiable. When used, an r-value reference behaves like a regular reference. Note, moving a primitive type is the same as copying it. The crux is in function overloading; consider the following from std::vector : void push_back (const value_type& val); void push_back (value_type&& val); // New in C++11 vec.push_back(getA());

  25. R-value reference What if we have a local reference that we want to move? Use std::move: A a; vec.push_back(std::move(a)); std::move is simply a cast from an l-value reference to an r-value reference: template <class T> typename remove_reference<T>::type&& move(T&& a) { return a; }

  26. R-value reference New standard class functions: move constructor and move assignment operator: class A { A(A&& a); // move constructor A& operator=(A&& a); // move assignment operator }; There exist defaulted versions of these functions. It works in many cases, but you notably shouldn t use them if your class owns raw pointers (like in basic data structures).

  27. R-value reference This is just the surface of r-value references! I won t have the time to cover forwarding references, how to write methods with this passed as an r-value reference, how to write efficient matrix multiplication by re-using the temporaries I could do a 3 hour presentation on r-value references alone, and this is not the place to do it. What you should remember is that the STL in C++11 and beyond is already using the r-value reference feature and the move semantics. Just upgrading to C++11 should give you a boost in performance. And I hoped I convinced you to read more on this topic!

  28. Lambda expressions Consider the following function from the STL: template< class ForwardIt, class UnaryPredicate > ForwardIt remove_if( ForwardIt first, ForwardIt last, UnaryPredicate p ); And the following usage: vector<uint64_t> vec; struct Predicate { bool operator()(const uint64_t& x) const { return 3*x < x*x; } } p; std::remove_if(vec.begin(), vec.end(), p);

  29. Lambda expressions C++11 has lambda expressions to create a function object in an expression. The syntax is: body> } [<capture-list>] (<parameter-list>) -> <trailing-return-type> { <function- The previous example becomes: vector<uint64_t> vec; std::remove_if(vec.begin(), vec.end(), [](const uint64_t& x) -> bool { return 3*x < x*x; }); A lambda-expression can also be used to initialize a variable: auto lambda = [](const uint64_t& x) -> bool { return 3*x < x*x; }; Note that lambda is not a free function; it is an auto-type object with an overloaded operator()

  30. Lambda expressions Let s take a look at the capture-list part of the expression: body> } [<capture-list>] (<parameter-list>) -> <trailing-return-type> { <function- The capture-list allows to capture variable accessible in the scope where the lambda expressions is defined: vector<uint64_t> vec; uint64_t a = 19; uint64_t b = 21; std::remove_if(vec.begin(), vec.end(), [a, &b](const uint64_t& x) -> bool { return a*b*x < x*x; }); Note that variables are captured by value by default; a variable must be preceded by & to be captured by reference.

  31. Lambda expressions Always remember that lambda-expressions create objects in C++, not a function. In this initialization: auto lambda = [](const uint64_t& x) -> bool { return 3*x < x*x; }; In this example, lambda is the name of a variable, not the name of a function. A counter-intuitive consequence of this is the interdiction of recursive-lambdas (i.e., what looks like recursive function calls, but is not). See this paper to understand why: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0839r0.html Maybe in C++20

  32. Generalized constant expressions Constant doesn t always mean the compiler will evaluate it at compile time. Consider this: struct S { static const int size; }; const int limit = 2 * S::size; const int S::size = 256; When is limit initialized? Why?

  33. Generalized constant expressions The problem is the absence of reliable way to assert that an expression has a constant value. That s why a new keyword was introduced : constexpr In C++11, a constexpr function needs to satisfy these constraints (from the original proposal): A function is a constant-expression function if it returns a value (i.e., has non-voidreturn type); its body consists of a single statement of the form return expr; where after substitution of constant expression for the function parameters in expr, the resulting expression is a constant expression (possibly involving calls of previously defined constant expression functions); and it is declared with the keyword constexpr

  34. Generalized constant expressions struct S { constexpr int twice(); private: static constexpr int val; }; constexpr int S::val = 7; constexpr int S::twice() { return val + val; } Requirements for constexpr are strict in C++11. Maybe the future will give us more capabilities.

  35. Range-based for A classic iteration loop: vector<int> vec = get_big_vector(); for( vector<int>::iterator itr = vec.begin(); itr != vec.end(); ++itr) { // Where s the inefficiency? do_something(*itr); } With C++11 improvements: vector<int> vec = get_big_vector(); for( auto itr = vec.begin(); itr != vec.end(); ++itr) { do_something(*itr); }

  36. Range-based for Using only a range-based for: vector<int> vec = get_big_vector(); for( int i : vec) { do_something(i); } With some C++11 improvements: vector<int> vec = get_big_vector(); for( auto i : vec) { do_something(i); }

  37. Range-based for The preferred way to do it with C++11: vector<int> vec = get_big_vector(); for( const auto& i : vec) { do_something(i); } You remembered what I said about auto right?

  38. Null pointer constant A small change in how to assign and compare null pointers: A* ptr = nullptr; if(ptr == nullptr) {} This looks like a cosmetic change, but nullptr is not an integral type: it s a pointer type. It s preferred to use nullptr over NULL and 0.

  39. Raw-literal strings const char* str = R"str(Is there any better way to explain what a raw-literal string is than to draw a slide with a raw-literal string? If you are an ounce like me , you have been dreaming of this day for a decade. It s definitely not that I have a deep affection for slides in general, but I do love to have self-contained unit tests with complex input. But that's a story for another time...)str";

  40. C++11 bis (Let s not forget about the library)

  41. C++14

  42. Binary literals decltype(auto) Return type deduction for normal functions Initialized lambda captures Generic lambdas Variable templates Relaxing requirements on constexpr functions Member initializers and aggregates [[deprecated]] attribute Single quotation mark as digit separator C++ sized deallocation That s all folks!

  43. Single quotation mark as digit separator Convenient for big numbers: uint64_t i = 1 000 000 000 000;

  44. Binary literals For all the bitmasks you want: uint64_t i = 0b01010101; uint64_t j = 0B01010101;

  45. Relaxing requirements on constexpr functions constexpr functions were relaxed to (from N3652): Allow declarations within constexpr functions, other than: static or thread_local variables uninitialized variables Allow if and switch statements (but not goto) Allow all looping statements: for (including range-based for), while, and do-while Allow mutation of objects whose lifetime began within the constant expression evaluation.

  46. Relaxing requirements on constexpr functions This is valid C++ 14! constexpr uint64_t slow_add(uint64_t n) { uint64_t sum = 0; for(uint64_t i = 0; i < n; ++i) { sum += i; } return sum; }

  47. C++17

  48. static_assert with no message Direct-list initialization of enums Disabling trigraph extension by default Hexadecimal floating-point literals typename in a template template parameter Using attributes namespaces without repetition New auto rules for direct-list-initialization Dynamic memory allocation for over-aligned data Fold expressions Template argument deduction for class templates U8 character literals Non-type template parameters with auto type Nested namespace definition Guaranteed copy elision Attributes for namespaces and enumerators Stricter expression evaluation order Allow constant evaluation for all non-type template arguments Requirement to ignore unknown attributes Remove deprecated register storage class constexpr if-statement Remove deprecated bool increment Inline variables Make exception specifications part of the type system Structured bindings _has_include in preprocessor conditionals Separate variable and conditions for if and switch Changes in inheriting constructors Matching template template parameters to compatible arguments [[fallthrough]], [[nodiscard]], [[maybe_unused]] attributes Removing deprecated dynamic exception specifications Aggregate initialization of classes with base classes Pack expansions in using-declarations constexpr lambda expressions Differing begin and end types in range-based for Lambda capture of *this

  49. Interlude

  50. What was exciting in C++ in each revision? Was C++11 enough? Should you upgrade to C++14 or C++17? What needs to be next?

More Related Content