1.46k likes | 1.51k Views
Advanced Programming in C++. Organization. 2/ 2 Z/Zk Lecture mixture of lecturers "Seminars" only 5 sessions during the semester dedicated to 3 homework assignments before : assignment, explanation, hints after : evaluation, frequent errors, best solutions Credit
E N D
Advanced Programming in C++ NPRG041 Programming in C++ - 2016/2017 David Bednárek
Organization • 2/2 Z/Zk • Lecture • mixture of lecturers • "Seminars" • only 5 sessions during the semester • dedicated to 3homework assignments • before: assignment, explanation, hints • after: evaluation, frequent errors, best solutions • Credit • at least15 points from assignments • required before exam • Exam • homeworks: 27..30pts ⇉ 1, 23..26pts ⇉ 2, 19..22pts ⇉ 3 • if you are happy with this result, you may skip the oral part • oral exam ⇉ ±10pts • beware: it may improve or worsen your result
Homework assignments and seminars • week 6 • March 26 Seminar: Assignment #1 • week 8 • April 9 Seminar:Assignment #2 • April 11: Deadline #1 • week 10 • April 23 Seminar:Assignment #3, Evaluation #1 • April 24: Deadline #2 • week 12 • May 7 Seminar: Evaluation #2 • May 8: Deadline #3 • week 14 • May 21 Seminar: Evaluation #3 • Deadlines: Wednesdaysat 23:59 local time • submit the required files to ReCodeX • late submissions = 1 point per each day deduced
Books C++11/14/17/20 en.cppreference.com/w/cpp Scott Meyers: Effective Modern C++ (C++11/C++14) O'Reilly 2014 Methodology of programming Scott Meyers: Overview of the New C++ (C++11) Collected slides Motivation behind C++11 explained Bjarne Stroustrup: The C++ Programming Language - 4th Edition Addison-Wesley 2013 Complete C++11 textbook Stanley B. Lippman, Josée Lajoie, Barbara E. Moo: C++ Primer (5th Edition) Addison-Wesley 2012 http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list
Older books • Intermediate – before C++11 = outdated but may be worth reading • Andrei Alexandrescu, HerbSutter:C++ CodingStandards (2005) • ScottMeyers:Effective C++ (1998)More Effective C++ (1996)Effective STL (2001) • Herb Sutter:Exceptional C++ (2000)More Exceptional C++ (2002)Exceptional C++ Style (2004) • Nicolai M. Josuttis:Object-OrientedProgramming in C++ (2002)The C++ Standard Library (1999)
Why exceptions? Returning error codes error_code f() { auto rc1 = g1(); if ( rc1.bad() ) return rc1; auto rc2 = g2(); if ( rc2.bad() ) return rc2; return g3(); } Run-time cost smallif everything is OK smallif something wrong Throwing exceptions void f() { g1(); g2(); g3(); } Run-time cost none if everything is OK big if something wrong
Exception handling Exceptions are "jumps" Start: throwstatement Destination: try-catch block Determined in run-time The jump may exit a procedure Local variables will be properly destructed by destructors Besides jumping, a value is passed The type of the value determines the destination Typically, special-purpose classes Catch-block matching can understand inheritance class AnyException{ /*...*/ }; class WrongException : public AnyException { /*...*/ }; class BadException : public AnyException { /*...*/ }; void f() { if ( something == wrong ) throw WrongException( something); if ( anything != good ) throw BadException( anything); } void g() { try { f(); } catch ( const AnyException & e1 ) { /*...*/ } }
Exception handling Exceptions are "jumps" Start: throwstatement Destination: try-catch block Determined in run-time The jump may exit a procedure Local variables will be properly destructed by destructors Besides jumping, a value is passed The type of the value determines the destination Typically, special-purpose classes Catch-block matching can understand inheritance The value may be ignored class AnyException{ /*...*/ }; class WrongException : public AnyException { /*...*/ }; class BadException : public AnyException { /*...*/ }; void f() { if ( something == wrong ) throw WrongException(); if ( anything != good ) throw BadException(); } void g() { try { f(); } catch ( const AnyException &) { /*...*/ } }
Exception handling Exceptions are "jumps" Start: throwstatement Destination: try-catch block Determined in run-time The jump may exit a procedure Local variables will be properly destructed by destructors Besides jumping, a value is passed The type of the value determines the destination Typically, special-purpose classes Catch-block matching can understand inheritance The value may be ignored There is an universal catch block class AnyException{ /*...*/ }; class WrongException : public AnyException { /*...*/ }; class BadException : public AnyException { /*...*/ }; void f() { if ( something == wrong ) throw WrongException(); if ( anything != good ) throw BadException(); } void g() { try { f(); } catch (...) { /*...*/ } }
Exception handling Exception handling Evaluating the expression in the throw statement The value is stored "somewhere" Stack-unwinding Blocks and functions are being exited Local and temporary variables are destructed by calling destructors (user code!) Stack-unwinding stops in the try-block whose catch-block matches the throw expression type catch-block execution The throw value is still stored may be accessed via the catch-block argument (typically, by reference) "throw;" statement, if present, continues stack-unwinding Exception handling ends when the accepting catch-block is exited normally Also using return, break, continue, goto Or by invoking another exception
Exception handling Materialized exceptions std::exception_ptr is a smart-pointer to an exception object Uses reference-counting to deallocate std::current_exception() Returns (the pointer to) the exception being currently handled The exception handling may then be ended by exiting the catch-block std::rethrow_exception( p) (Re-)Executes the stored exception like a throw statement This mechanism allows: Propagating the exception to a different thread Signalling exceptions in the promise/future mechanism std::exception_ptr p; void g() { try { f(); } catch (...) { p = std::current_exception(); } } void h() { std::rethrow_exception( p); } C++11
Exception handling Throwing and handling exceptions is slower than normal execution Compilers favor normal execution at the expense of exception-handling complexity Use exceptions only for rare events Out-of-memory, network errors, end-of-file, ... Mark procedures which cannot throw by noexcept void f() noexcept { /*...*/ } it may make code calling them easier (for you and for the compiler) noexceptmay be conditional template< typename T> void g( T & y) noexcept( std::is_nothrow_copy_constructible< T>::value) { T x = y; }
Exception handling Mark procedures which cannot throw by noexcept Example: Resizing std::vector<T> When inserting above capacity, the contents must be relocated to a larger memory block Before C++11, the relocation was done by copying, i.e. calling T(constT &) If a copy constructor threw, the new copies were discarded and the insert call reported failure by throwing Thus, if the insert threw, no observable change happened Note: Correct destruction of copies is possible only if the destructor is not throwing: ~T() noexcept In C++11, the relocation shall be done by moving If a move constructor throws, the previously moved elements shall be moved back, but it can throw again! The relocation is done by moving only if the move constructor is declared as T(T &&) noexcept ... or if it is declared implicitly and all elements satisfy the same property Otherwise, the slower copy method is used!
Exception handling Standard exceptions <stdexcept> All standard exceptions are derived from classexception the member function what()returns the error message bad_alloc: not-enough memory bad_cast: dynamic_cast on references Derived from logic_error: domain_error, invalid_argument, length_error, out_of_range e.g., thrownby vector::at Derived from runtime_error: range_error, overflow_error, underflow_error Hard errors (invalid memory access, division by zero, ...) are NOT signalized as exceptions These errors might occur almost anywhere The need to correctly recover via exception handling would prohibit many code optimizations Nevertheless, there are (proposed) changes in the language specification that will allow reporting hard errors by exceptions at reasonable cost
Exception-safe programming Bezpečné programování s výjimkami
Using throw a catch is simple Producing code that works correctly in the presence of exceptions is hard Exception-safety Exception-safe programming void f() { int * a = new int[ 100]; int * b = new int[ 200]; g( a, b); delete[] b; delete[] a; } If new int[ 200] throws, the int[100] block becomes inaccessible If g() throws, two blocks become inaccessible Exception-safe programming
void f() { int * a = new int[ 100]; int * b = new int[ 200]; g( a, b); delete[] b; delete[] a; } If new int[ 200] throws, the int[100] block becomes inaccessible If g() throws, two blocks become inaccessible Safety is expensive void f() { int * a = new int[ 100]; try { int* b = new int[ 200]; try { g( a, b); } catch (...) { delete[] b; throw; } delete[] b; } catch (...) { delete[] a; throw; } delete[] a; } Exception-safe programming
void f() { int * a = new int[ 100]; int * b = new int[ 200]; g( a, b); delete[] b; delete[] a; } If new int[ 200] throws, the int[100] block becomes inaccessible If g() throws, two blocks become inaccessible Smart pointers can help void f() { auto a = std::make_unique<int[]>(100); auto b = std::make_unique<int[]>(200); g( &*a, &*b); } Exception processing correctly invokes the destructors of smart pointers Exception-safe programming
There are more problems besides memory leaks std::mutex my_mutex; void f() { my_mutex.lock(); // do something critical here my_mutex.unlock(); // something not critical } If something throws in the critical section, this code will leave the mutex locked forever! RAII: Resource Acquisition Is Initialization Constructor grabs resources Destructor releases resources Also in the case of exception std::mutex my_mutex; void f() { { std::lock_guard< std::mutex> lock( my_mutex); // do something critical here } // something not critical } There is a local variable “lock” that is never (visibly) used beyond its declaration! Nested blocks matter! Exception-safe programming
An incorrectly implemented copy assignment T & operator=( const T & b) { if ( this != & b ) { delete body_; body_ = new TBody( b.length()); copy( * body_, * b.body_); } return * this; } Produces invalid object when TBody constructor throws Does not work when this==&b Exception-safe implementation T & operator=( const T & b) { T tmp(b); swap(tmp); return * this; } void swap( T & b) { std::swap( body_, b.body_); } Can reuse code already implemented in the copy constructor and the destructor Does work when this==&b Exception-safe programming
Exception-safe programming Language-enforced rules Destructors may not end by throwing an exception Constructors of static variables may not end by throwing an exception Copying of exception objects may not throw Compilers sometimes generate implicit try-catch blocks When constructing a compound object, a constructor of an element may throw Array allocation Class constructors The implicit catch block destructs previously constructed parts and rethrows
Exception-safe programming Theory (Weak) exception safety Exceptions does not cause inconsistent state No memory leaks No invalid pointers Application invariants hold ...? Strong exception safety Exiting function by throwing means no change in (observable) state Observable state = public interface behavior Also called "Commit-or-rollback semantics"
Strong exception safety • void f() • { • g1(); • g2(); • } • When g2() throws... • f() shall signal failure (by throwing) • failure shall imply no change in state • but g1() already changed something • it must be undone • void f() • { • g1(); • try { • g2(); • } catch(...) { • undo_g1(); • throw; • } • } NPRG041 Programming in C++ - 2016/2017 David Bednárek
Strong exception safety • Undoing is sometimes impossible • e.g. erase(...) • Code becomes unreadable • Easy to forgot the undo • Observations • If a function does not change observable state, undo is not required • The last function in the sequence is never undone • void f() • { • g1(); • try { • g2(); • try { • g3(); • } catch(...) { • undo_g2(); • throw; • } • } catch(...) { • undo_g1(); • throw; • } • } NPRG041 Programming in C++ - 2016/2017 David Bednárek
Strong exception safety • Check-and-do style • Check if everything is correct • Then do everything • These functions must not throw • Still easy to forget a check • Work is often duplicated • It may be difficult to write non-throwing do-functions • void f() • { • check_g1(); • check_g2(); • check_g3(); • do_g1(); • do_g2(); • do_g3(); • } NPRG041 Programming in C++ - 2016/2017 David Bednárek
Strong exception safety • Check-and-do with tokens • Each do-function requires a token generated by the check-function • Checks can not be omitted • Tokens may carry useful data • Duplicate work avoided • It may be difficult to write non-throwing do-functions • void f() • { • auto t1 = check_g1(); • auto t2 = check_g2(); • auto t3 = check_g3(); • do_g1( t1); // or t1.doit(); • do_g2( t2); • do_g3( t3); • } NPRG041 Programming in C++ - 2016/2017 David Bednárek
Strong exception safety • Prepare-and-commit style • Prepare-functions generate a token • Tokens must be committed to produce observable change • Commit-functions must not throw • If not committed, destruction of tokens invokes undo • If some of the commits are forgotten, part of the work will be undone • void f() • { • auto t1 = prepare_g1(); • auto t2 = prepare_g2(); • auto t3 = prepare_g3(); • commit_g1( t1); // or t1.commit(); • commit_g2( t2); • commit_g3( t3); • } NPRG041 Programming in C++ - 2016/2017 David Bednárek
Strong exception safety • Two implementations: • Do-Undo • Prepare-functions make observable changes and return undo-plans • Commit-functions clear undo-plans • Token destructors apply undo-plans • Prepare-Commit • Prepare-functions return do-plans • Commit-functions perform do-plans • Token destructors clear do-plans • Commits and destructors must not throw • Unsuitable for inserting • Use Do-Undo when inserting • Destructor does erase • Use Prepare-Commit when erasing • Commit does erase • void f() • { • auto t1 = prepare_g1(); • auto t2 = prepare_g2(); • auto t3 = prepare_g3(); • commit_g1( t1); // or t1.commit(); • commit_g2( t2); • commit_g3( t3); • } NPRG041 Programming in C++ - 2016/2017 David Bednárek
Strong exception safety • Problems: • Some commits may be forgotten • Do-Undo style produces temporarily observable changes • Unsuitable for parallelism • Atomic commit required • Prepare-functions concatenate do-plans • Commit executes all do-plans "atomically" • It may be wrapped in a lock_guard • Commit may throw! • It is the only function with observable effects • Inside commit • Do all inserts • If some fails, previous must be undone • Do all erases • Erases do not throw (usually) • Chained style • void f() • { • auto t1 = prepare_g1(); • auto t2 = prepare_g2( std::move(t1)); • auto t3 = prepare_g3( std::move(t2)); • t3.commit(); • } • Symbolic style • void f() • { • auto t1 = prepare_g1(); • auto t2 = std::move(t1) | prepare_g2(); • auto t3 = std::move(t2) | prepare_g3(); • t3.commit(); • } NPRG041 Programming in C++ - 2016/2017 David Bednárek
Templates Template a generic piece of code parameterized by types, class templates, and integer constants Class templates Global classes Classes nested in other classes, including class templates template< typename T, std::size_t N> class array { /*...*/ }; Function templates Global functions Member functions, including constructors template< typename T> inline T max( T x, T y) { /*...*/ } Type templates [C++11] template< typename T> using array3 = std::array< T, 3>; Variable templates [C++14] Global variables and static data members template< typename T> factory_class<T> factory;
Templates Template instantiation Using the template with particular type and constant parameters Class, type, and variable templates: parameters specified explicitly std::array< int, 10> x; Function templates: parameters specified explicitly or implicitly Implicitly - derived by compiler from the types of value arguments int a, b, c; a = max( b, c); // calls max< int> Explicitly a = max< double>( b, 3.14); Mixed: Some (initial) arguments explicitly, the rest implicitly std::array< int, 5> v; x = std::get< 3>( v); // calls std::get< 3, std::array< int, 5>>
Templates and compilation Compiles [may] check template code when defined Without the knowledge of template arguments Syntactic hints from the author may be required Compilers generate code only when templates are instantiated Different instantiations do not share code It sometimes causes unwanted code size explosion There is no run-time support required for templates Code generated for templates is identical to non-templated equivalents There is no performance penalty for generic code Except that generic programming may encourage programmers to be lazy
Writing templates Compiler needs hints from the programmer Dependent names have unknown meaning/contents template< typename T> class X { type names must be explicitly designated using U = typename T::B; typenameU::D p; // U is also a dependent name using Q = typenameY<T>::C; void f() { T::D(); } // T::D is not a type explicit template instantiations must be explicitly designated bool g() { return 0 < T::template h<int>(); } int j() { return p.template k<3>(); } // type of p is a dependent name } members inherited from dependent classes must be explicitly designated template< typename T> class X : public T { const int K = T::B + 1; // B is not directly visible although inherited void f() { return this->a; } // a is not directly visible }
Templates and compilation Implicit template instantiation When a class template specialization is referenced in context that requires a complete object type, or… … when a function template specialization is referenced in context that requires a function definition to exist… … the template is instantiated (the code for it is actually compiled) unless the template was already explicitly specialized or explicitly instantiated at link time, identical instantiations generated by different translation units are merged Instantiation of template member functions Instantiation of a class template doesn't instantiate any of its member functions unless they are also used The definition of a template must be visible at the point of implicit instantiation
Templates and compilation The definition of a template must be visible at its instantiation Classes and types The visibility of definition is required also in the non-template case Most class and type definitions must reside in header files Function templates (including non-template member functions of class templates etc.) The template visibility rule is equivalent to rules for inline functions Non-inline function templates are almost unusable Most function template definitions must reside in header files (and be inline) If only a declaration is visible, the compiler will not complain - it will generate a call but not the code of the function called – the linker will complain In rare cases, the visibility rule may be silenced by explicit instantiation: Applicable only if all required argument combinations may be enumerated template intmax<int>(int, int); // forces instantiation of max<int> template double max(double, double); // forces instantiation of max<double> template class X<int>; // forces instantiation of all member functions of X<int>
Declarations and definitions • Declaration • A construct to declare the existence (of a class/variable/function/...) • Identifier • Some basic properties • Ensures that (some) references to the identifier may be compiled • Some references may require definition • Definition • A construct to completely define (a class/variable/function/...) • Class contents, variable initialization, function implementation • Ensures that the compiler may generate runtime representation • Every definition is a declaration • Declarations allow (limited) use of identifiers without definition • Independent compilation of modules • Solving cyclic dependences • Minimizing the amount of code that requires (re-)compilation
Declarations and definitions • One-definition rule #1: • One translation unit... • (module, i.e. one .cpp file and the .hpp files included from it) • ... may contain at most one definition of any item • One-definition rule #2: • Program... • (i.e.the .exe file including the linked .dll files) • ... may contain at most one definition of a variableor a non-inline function • Definitions of classes, typesor inline functionsmay be contained more than once (due to inclusion of the same .hpp file in different modules) • If these definitions are not identical, undefined behavior will occur • Beware of version mismatch between headers and libraries • Diagnostics is usually poor (by linker)
Cyclic references in code • Class B refers to A • structB { • B( A * q) : link{q} {} • intget_v() • { • return link->v; • } • A * link; • } • Declarations of constructor and link require declaration of A • Therefore definition of B requires declaration of A • Definition of get_vrequires definition of A • Class A refers to B • struct A { • A( int p) : v{p} {} • B generate_b() • { • return B(this); • } • int v; • } • Declaration of generate_b requires declaration of B • Therefore definition of A requires declaration of B • Definition of generate_b requires definition of B
Cyclic references in code • A correct ordering • There are more possible • Declaration of B • Definition of A • Except definition of generate_b • Definition of B • Definition of generate_b • struct B; • struct A { • A( int p) : v{p} {} • B generate_b(); • int v; • } • struct B { • B( A * q) : link{q} {} • intget_v() • { • return link->v; • } • A * link; • } • inline B A::generate_b() • { • return B(this); • }
Cyclic references between headers • B.hpp • #ifndefB_hpp_ • #define B_hpp_ • #include “A.hpp” • structB { • B( A * q) : link{q} {} • intget_v() { • return link->v; • } • A * link; • } • #endif • WRONG! • When B.hpp is compiled, ifndef guards prohibit recursive B.hpp inclusion from A.hpp • Definition of A will not see the declaration of B • A.hpp • #ifndefA_hpp_ • #define A_hpp_ • #include “B.hpp” • struct A { • A( int p) : v{p} {} • B generate_b(); • int v; • } • inline B A::generate_b() { • return B(this); • } • #endif • WRONG! • When A.hpp is compiled, ifndef guards prohibit recursive A.hpp inclusion from B.hpp • Definition of B will not see the definition of A
Cyclic references between headers • B.hpp • #ifndefB_hpp_ • #define B_hpp_ • #include “A.hpp” • structB { • B( A * q) : link{q} {} • intget_v() { • return link->v; • } • A * link; • } • #endif • STILL WRONG! • B.hpp includes TOO MUCH • A.hpp contains the definition of generate_b which cannot compile before definitionof B • A.hpp • #ifndefA_hpp_ • #define A_hpp_ • struct B; • struct A { • A( int p) : v{p} {} • B generate_b(); • int v; • } • #include “B.hpp” • inline B A::generate_b() { • return B(this); • } • #endif • A ticket to a madhouse • Never bury include directives inside header or source files
Cyclic references between headers • B.hpp • #include “A.hpp” • #ifndefB_hpp_ • #define B_hpp_ • structB { • B( A * q) : link{q} {} • intget_v() { • return link->v; • } • A * link; • } • #endif • It works, but your colleagues will want to kill you • Never use define guards for anything else than complete header files • including the include directives • A.hpp • #ifndefA_defined • #define A_defined • struct B; • struct A { • A( int p) : v{p} {} • B generate_b(); • int v; • } • #endif • #ifndefgenerate_b_defined • #define generate_b_defined • #include “B.hpp” • inline B A::generate_b() { • return B(this); • } • #endif
Cyclic references between classes – solved with .cpp files • B.hpp • #ifndefB_hpp_ • #define B_hpp_ • #include “A.hpp” • structB { • B( A * q) : link{q} {} • intget_v() { • return link->v; • } • A * link; • } • #endif • Still problematic • Including A.hpp enable you to call generate_b which returns undefined class B • A.hpp • #ifndefA_hpp_ • #define A_hpp_ • struct B; • struct A { • A( int p) : v{p} {} • B generate_b(); • int v; • } • #endif • A.cpp • #include “A.hpp” • #include “B.hpp” • inline B A::generate_b() { • return B(this); • }
Cyclic references between classes – solved with more .hpp files • Btypes.hpp • #ifndefBtypes_hpp_ • #define Btypes_hpp_ • struct A; • structB { • B( A * q) : link{q} {} • intget_v() { • return link->v; • } • A * link; • } • #endif • B.hpp • #ifndefB_hpp_ • #define B_hpp_ • #include “Btypes.hpp” • #include “A.hpp” • inline int B::get_v() { • return link->v; • } • #endif • Instruct everybody to include A.hpp or B.hpp • Otherwise they may miss some inline definition • Which results in linker error if called • Atypes.hpp • #ifndefAtypes_hpp_ • #define Atypes_hpp_ • structB; • struct A { • A( int p) : v{p} {} • B generate_b(); • int v; • } • #endif • A.hpp • #ifndefA_hpp_ • #define A_hpp_ • #include “Atypes.hpp” • #include “B.hpp” • inline B A::generate_b() { • return B(this); • } • #endif • Never include Atypes.hpp or Btypes.hpp directly • Except in the corresponding A.hpp and B.hpp
Cyclic references between templates – solved with more .hpp files • Btypes.hpp • #ifndefBtypes_hpp_ • #define Btypes_hpp_ • template< typename T> struct A; • template< typename T> structB { • B( A<T> * q) : link{q} {} • T get_v() { • return link->v; • } • A<T> * link; • } • #endif • B.hpp • #ifndefB_hpp_ • #define B_hpp_ • #include “Btypes.hpp” • #include “A.hpp” • template< typename T> inline T B<T>::get_v() { • return link->v; • } • #endif • Instruct everybody to include A.hpp or B.hpp • Otherwise they may miss some inline definition • Which results in linker error if called • Atypes.hpp • #ifndefAtypes_hpp_ • #define Atypes_hpp_ • template< typename T> structB; • template< typename T> structA { • A( T p) : v{p} {} • B<T> generate_b(); • T v; • } • #endif • A.hpp • #ifndefA_hpp_ • #define A_hpp_ • #include “Atypes.hpp” • #include “B.hpp” • template< typename T> inline B<T> A<T>::generate_b() { • return B<T>(this); • } • #endif • Never include Atypes.hpp or Btypes.hpp directly • Except in the corresponding A.hpp and B.hpp
Variadic templates Template heading Allows variable number of type arguments template< typename... TList> class C { /* ... */ }; typename ... declares named template parameter pack may be combined with regular type/constant arguments template< typename T1, int c2,typename... TList> class D { /* ... */ }; also in partial template specializations template< typename T1, typename... TList> class C< T1, TList...>{ /* ... */ }; C++11