Slicing Imagine we have a hierarchy of shape shapes with the - - PowerPoint PPT Presentation

slicing
SMART_READER_LITE
LIVE PREVIEW

Slicing Imagine we have a hierarchy of shape shapes with the - - PowerPoint PPT Presentation

Slicing Imagine we have a hierarchy of shape shapes with the following inherits members get_name(): circle triangle the name assigned to the shape get_area(): the area occupied by the shape We also provide swap(&,


slide-1
SLIDE 1

Slicing

  • Imagine we have a hierarchy of

shapes with the following members

  • get_name():

the name assigned to the shape

  • get_area():

the area occupied by the shape

  • We also provide swap(&, &) for exchanging two shapes of the same

type

  • Let us implement bubble sort
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

356

shape

inherits

circle triangle …

slide-2
SLIDE 2

Bubble Sort for a vector<shape*>

  • When we invoke swap, we exchange the contents of the shapes at

v[i] and v[i+1]

  • To do this, swap(shape&, shape&) is invoked
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

357

void sort(vector<shape*> &v) { if (v.size() < 2) return; bool rerun; do { rerun = false; for (size_t i = 0; i < v.size()-1; ++i) { if (v[i]->get_area() > v[i+1]->get_area()) { swap(*v[i], *v[i+1]); rerun = true; } } } while (rerun); }

slide-3
SLIDE 3

Bubble Sort for a vector<shape*> (cont’d)

  • Since the swap function only has space for a shape inside t,
  • nly the shape part of s1 is assigned to t
  • Only the shape part of s2 can be assigned to s1
  • During runtime s1 and s2 can be objects of different types
  • It is not possible to assign the contents of a circle to a rectangle
  • Finally, the shape part of t is assigned to s2
  • Members (i.e., radius, sides a+b+c) used to compute the area of the shape are

not swapped

  • Hence, only the name is swapped, and sort will not terminate, because the

areas of the shapes are never swapped

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

358

void swap(shape &s1, shape &s2) { shape t = s1; s1 = s2; s2 = t; }

slide-4
SLIDE 4

Bubble Sort for vector<shape*>

  • We should have only exchanged the pointers in the vector
  • Providing swap for shapes,

was probably misguided in the first place

  • It makes only sense to swap shapes of the very same type
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

359

void sort(vector<shape*> &v) { if (v.size() < 2) return; bool rerun; do { rerun = false; for (size_t i = 0; i < v.size()-1; ++i) { if (v[i]->get_area() > v[i+1]->get_area()) { swap(v[i], v[i+1]); // swap(*v[i], *v[i+1]); rerun = true; } } } while (rerun); }

slide-5
SLIDE 5

Advanced Software Engineering with C++ Templates

Liskov Substitution Principle Thomas Gschwind <thgatzurichdotibmdotcom>

slide-6
SLIDE 6

Liskov Substitution Principle

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

361

slide-7
SLIDE 7

Squares and Rectangles

  • I had a colleague who was unsure whether to derive rectangle from

square or vice-versa. What would you recommend to him? Implement a set of sample programs illustrating various options and your recommendation! The programs should demonstrate why your solution is better than the other solutions

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

362

slide-8
SLIDE 8

Squares and Rectangles

  • Class hierarchy for a vector graphics program
  • We have an abstract class Shape from which various geometric

shapes are being derived

  • Task:
  • Add the following classes: Square, Rectangle
  • Also add useful members like {get,set}_{width,height,length,size}, …

Shape Line

inherits

Circle Triangle …

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

363

slide-9
SLIDE 9

Liskov Substitution Principle

If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behaviour of P is unchanged when o1 is substituted for o2 then S is a subtype of T. Put Simple: Every function/program must work the same when it is invoked with a subclass instead of the expected

  • class. This is the responsibility of the subclasses!
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

364

slide-10
SLIDE 10

Square  Rectangle

inherits

Rectangle Square

class Rectangle { int w, h; public: virtual void setWidth(int wi) { w=wi; } virtual void setHeight(int he) { h=he; } }; class Square : public Rectangle { public: virtual void setWidth(int w) { Rectangle::setHeight(w); Rectangle::setWidth(w); } virtual void setHeight(int h) { setWidth(h); } };

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

365

slide-11
SLIDE 11

Square  Rectangle (cont’d)

  • What happens if we pass a Square to a function test

expecting a Rectangle?

  • Can we expect the function test to know about

Square?

  • Would it be OK if the function yields an unexpected
  • utcome under the motto garbage-in garbage-out?
  • No! Because we claim that a Square is a Rectangle!

inherits

Rectangle Square

void test(Rectangle &r) { r.setWidth(5); r.setHeight(4); assert((r.getWidth()*r.getHeight())==20); }

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

366

slide-12
SLIDE 12

Square  Rectangle V2

inherits

Rectangle Square

class Rectangle { int w, h; public: void setWidth(int wi) { w=wi; } void setHeight(int he) { h=he; } }; class Square : public Rectangle { public: void setWidth(int w) { Rectangle::setHeight(w); Rectangle::setWidth(w); } void setHeight(int h) { setWidth(h); } };

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

367

slide-13
SLIDE 13

Square  Rectangle V2 (cont’d)

  • What happens if we pass a Square to a function test expecting a

Rectangle?

  • The program works now as expected
  • However, since set_width/set_height are no longer virtual our

Square is now a Rectangle ==> This is not the solution either

void test(Rectangle &r) { r.setWidth(5); r.setHeight(4); assert((r.getWidth()*r.getHeight())==20); }

inherits

Rectangle Square

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

368

slide-14
SLIDE 14

Rectangle  Square

  • Square has one field (height or width)
  • Rectangle adds another one for the other

dimension

  • Makes sense from a memory usage point of view
  • Square has a method to set the length
  • Rectangle adds methods for height and width
  • Also makes sense from the methods they provide
  • A Square does not need set_height & set_width

inherits

Square Rectangle

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

369

slide-15
SLIDE 15

Rectangle  Square

class Square { int l; public: virtual void setLength(int le) { l=le; } virtual int getLength() { return l; } }; class Rectangle : public Square { // reuse length as height int w; public: virtual void setLength(int le) { Square::setLength(le); setWidth(le); } virtual void setHeight(int he) { Square::setLength(he); } virtual void setWidth(int wi) { w=wi; } };

inherits

Square Rectangle

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

370

slide-16
SLIDE 16

Rectangle  Square (cont’d)

  • We cannot pass a Square to the test function
  • What if test would take Squares?
  • No problem either, LSP fulfilled

void test(Rectangle &r) { r.setWidth(5); r.setHeight(4); assert((r.getWidth()*r.getHeight())==20); }

inherits

Square Rectangle

void test(Square &s) { s.setLength(5); s.setLength(4); assert((s.getLength()*s.getLength())==16); }

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

371

slide-17
SLIDE 17

Rectangle  Square (cont’d)

  • Let us extend our Shape class hierarchy
  • Our customer requests a get_area() member

int Square::get_area() { return l*l; } int Rectangle::get_area() { return l*wi; } void foo(Square &s) { assert((s.get_length()*s.get_length()) ==s.get_area()); }

inherits

Square Rectangle

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

372

slide-18
SLIDE 18

Rectangle  Square (cont’d)

  • Let us extend our Shape class hierarchy
  • Our customer requests a get_area() member

int Square::get_area() { return l*l; } int Rectangle::get_area() { return l*wi; } void test(Square &s) { assert((s.get_length()*s.get_length()) ==s.get_area()); } void oh_no_not_another_test() { Rectangle r(3,4); test(r); }

inherits

Square Rectangle

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

373

slide-19
SLIDE 19

Square  Shape, Rectangle  Shape

Shape Line

inherits

Circle … Rectangle Square Correct, don’t use deep class hierarchies just because it is “object-oriented” programming (is it?) or because it’s “cool” or “professional” (or whatever)!

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

374

slide-20
SLIDE 20

Lessons Learned

  • Avoid code inheritance
  • People will nail you down on how the base class is implemented
  • If inheritance is needed define an interface
  • Now, only the documentation counts
  • Inherit the interface not the code
  • If code inheritance is needed, prefer the use of an interface plus

use aggregation

  • Follow this advice especially when using Java
  • Yes, you need to type a bit more
  • If this is really (really?) not an option provide both an interface and

a class to inherit from

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

375

slide-21
SLIDE 21

Advanced Software Engineering with C++ Templates

Exceptions Thomas Gschwind <thgatzurichdotibmdotcom>

slide-22
SLIDE 22

Exceptions

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

377

  • Error Handling
  • Exception Specification
  • Exception Discrimination
  • Cleaning up in C++

(a.k.a. finally)

  • Standard Exceptions
slide-23
SLIDE 23

Other Error Handling Techniques

  • Return special value
  • return -1; or return NULL;
  • In case debatable whether this is an error and special value is available
  • For instance, finding element in collection,

amap.find(…) returns amap.end() if element cannot be found

  • Set a special variable (e.g., errno in C)
  • Can be useful if no “special return value” is available
  • Needs to be checked by caller after every call
  • Terminate the program
  • exit(error_code);
  • In case data structures are seriously corrupted and

continuing will create a bigger problem

  • Callback routine
  • If there is the chance that the error can be corrected
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

378

slide-24
SLIDE 24

Exceptions

  • Stack unwinding technique
  • Generated (thrown) as soon as an error is detected
  • Caught from an error handling “routine” (better block)
  • Integrated with destructor handling simplifying resource management
  • Can be used for “optimization” (avoid)
  • Return from a deeply nested invocation
  • Depends on architecture
  • Be careful when mixing code compiled with C and C++
  • However, as long as you only invoke C routines from C++ all is fine
  • Some really totally outdated 20 year old C++ compilers may have

problems with exceptions

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

379

slide-25
SLIDE 25

380

Exceptions (cont’d)

class MathErr {}; class ZeroDivide: public MathErr {}; fraction.{cc,h} fraction operator/(const fraction &a, fraction b) { if (b.c==0) throw ZeroDivide(); swap(b.c,b.d); return a*b; } fraction a(0), b, c, r;

// …

try { r=b+a*sqrt(sq(b)-4*c))/(2*a); } catch (ZeroDivide &zd) {

// …

} test.cc Invokes destructors of variables leaving scope Invokes directly the exception handler More readable and more efficient, especially, if it spans multiple function invocations

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.
slide-26
SLIDE 26

Exception Specification

  • If no exceptions are specified, a routine can throw any exception
  • Exceptions can be specified as part of the signature
  • void foo(…) throw (e1, e2, …) { … }
  • C++11: this feature has been deprecated due to “lack of success”:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3051.html

  • If no exception is thrown, this needs to be specified explicitly
  • void foo(…) throw ()
  • C++11: void foo(…) noexcept
  • C++11, noexcept can take a constexpr as argument indicating whether

the function may throw an exception (true) or not (false)

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

381

slide-27
SLIDE 27

Exception Specification (cont’d)

class Matherr {}; class ZeroDivide: public MathErr {}; fraction.{cc,h} fraction operator/(const fraction &a, fraction b) throw (ZeroDivide) { if (b.c==0) throw ZeroDivide(); swap(b.c,b.d); return a*b; } fraction a, b, c, r;

// …

try { r=b+a*sqrt(sq(b)-4*c))/(2*a); } catch (ZeroDivide &zd) {

// …

} test.cc

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

382

slide-28
SLIDE 28

Exception Specification (cont’d)

  • Why is it optional in C++? Why is it deprecated in C++11?
  • In general, the compiler cannot always check them
  • Multiple object files (from different sources)
  • Derived classes
  • Handling unexpected exceptions
  • unexpected()

(can be set with the set_unexpected() function)

  • Typically, this handler invokes std::terminate()
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

383

slide-29
SLIDE 29

Exception Specification: Multiple Object Files

  • Initially, all is fine
  • What if, due to new requirements, your

application needs V1.1 of Library Y

  • Version 1.1 changes some of the exceptions
  • Yes, should not have happened (it did anyways)
  • Options
  • Not implement the new requirement
  • Rewrite Library X (good luck if closed source)
  • Simply don’t take exceptions that serious (yes, this is ugly!)

Your Application Library X V1.0 Library Y V1.0 uses uses Library Y V1.1 upgrade uses

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

384

slide-30
SLIDE 30

Exception Specification: Mult. Object Files (cont’d)

  • Java
  • Since Library X won’t be recompiled, all is “fine”

(in the current Java version)

  • What if Library Y throws a new exception? Since we rely on the “old”

exception specifications of Library X, it can happen that sometimes an exception not listed in Library X’s interface description is thrown

  • Probably our application does not catch the exception and the application is

terminated.

  • C++
  • That’s what unexpected is good for
  • However, handling the problem in unexpected has proven

difficult/impossible which is one of the reasons this is deprecated in C++11

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

385

slide-31
SLIDE 31

Exception Specification: Derived Classes

  • The specification makes perfect sense
  • When writing to a file, an IO-Exception can occur

public class Writer { // … public void write(char[] buf) throws IOException { // … } // … }

Writer.java

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

386

slide-32
SLIDE 32

Exception Specification: Derived Classes (cont’d)

  • Sometimes, we don’t want to write to a file
  • What if the data generated needs to be processed again
  • We may want to write the data to the memory and process it from there
  • To achieve this, we derive a StringWriter from Writer

public class StringWriter extends Writer { // … public void write(char[] buf) throws IOException { // sometimes we need to allocate more memory // what if we are out of memory? } }

StringWriter.java

We would love to, but no, we cannot throw an OutOfMemoryException

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

387

slide-33
SLIDE 33

Exception Discrimination

  • Inheritance – order of catch clauses
  • Precisely the same as in Java or Python
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

388

slide-34
SLIDE 34

Exceptions (cont’d)

  • Catch all?
  • C++ has no common base class that can be used for all classes or exceptions
  • Yes, one can define such a class oneself

(be careful it may make your code worse than you may think)

  • catch(...) // yes three dots
  • What can be used as exception?
  • Any data type
  • Rethrowing an exception?
  • throw;
  • Call/Catch by value vs. call by reference
  • Give preference to T& over T
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

389

slide-35
SLIDE 35

finally

  • Some languages allow to provide a finally block
  • For clean up operations when the try or any catch/except block is left
  • C++ does not provide this feature
  • It can be confusing
  • It does not need it
  • C++ has something better …
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

390

Really? Really, really!

slide-36
SLIDE 36

finally: Some Java/Python Trivia

int foo(int x) throws Exception { if (x==0) return 0; if (x!=1) { try { if (x<4) throw new Exception("nix"); if (x==4) return 4; return 5; } catch (Exception e) { if (x==-1) throw new Exception("ni…ut"); if (x==2) return 2; } finally { return 3; } return 6; // dead code } return 1; }

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

392

def foo(x): if x == 0: return 0 if x != 1: try: if x < 4: raise Exception("nix") if x == 4: return 4; return 5 except Exception as e: if x == -1: raise Exception("nix gut") if x == 2: return 2 finally: return 3 return 6 return 1

slide-37
SLIDE 37

finally: some Java/Python Trivia

int foo(int x) throws Exception { if (x==0) return 0; if (x!=1) { try { if (x<4) throw new Exception("nix"); if (x==4) return 4; return 5; } catch (Exception e) { if (x==-1) throw new Exception("ni…ut"); if (x==2) return 2; } finally { return 3; } return 6; } return 1; }

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

393

def foo(x): if x == 0: return 0 if x != 1: try: if x < 4: raise Exception("nix") if x == 4: return 4; return 5 except Exception as e: if x == -1: raise Exception("nix gut") if x == 2: return 2 finally: return 3 return 6 return 1 As soon as this block is entered, 3 is

  • returned. No

exceptions are returned by foo. return 6 is dead code

slide-38
SLIDE 38

finally

  • Some languages allow to provide a finally block
  • For clean up operations when the try or any catch/except block is left
  • C++ does not provide this feature
  • It can be confusing
  • It does not need it
  • C++ has something better …

… and you know it

  • When is s allocated and when deallocated?
  • When it enters, respectively leaves the try block
  • YES, deterministically invoked destructors!
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

394

void foo() { try { String s="Hello World"; // problematic code } catch( /*…*/ ) { // exception handler } }

slide-39
SLIDE 39

Cleaning Up in C++

struct DBLocker { string res; DBLocker(string lockme) : res(lockme) { lock(lockme); } ~DBLocker() { unlock(res); } } void foo() { try { DBLocker lock("whatever we want to lock"); // problematic code } catch ( /*…*/ ) { // exception handler } }

foo.cc

When this block is left, the destructor will be executed and our external resource will be unlocked.

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

395

slide-40
SLIDE 40

Cleaning Up in C++11: A Variant

template<typename F> struct final_action { F clean; final_action(F f) : clean(f) {} ~final_action() { clean(); } } template<class F> final_action<F> finally(F f) { return final_action<F>(f); } void foo() { try { auto finally_action = finally([&]{ /* handler */ }); // problematic code } catch ( /*…*/ ) { // exception handler } }

foo.cc

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

396

slide-41
SLIDE 41

Cleaning Up in C++11: A Variant (cont’d)

  • Lambda function allows to specify the cleanup code within the

function

  • In certain cases, this may be more readable
  • For instance, if no resource allocation/deallocation pair can be identified
  • Implementation-wise, same approach as before
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

397

slide-42
SLIDE 42

Optimizing with Exceptions

  • Although as a construct to use with care, this sample is shown in

Stroustrup’s the C++ Programming Language (3rd ed)

  • I have seen it in a program
  • My take, don’t do this; typically, checking for an empty queue or upfront

the number of elements is not expensive

void for_each(Queue &q) { try { for(;;) { int i=q.get(); // throws 'Empty' if empty // … } } catch (Queue::Empty) { return; } }

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

398

slide-43
SLIDE 43

Standard Exceptions (std::exception)

  • std::bad_alloc –

thrown by new on allocation failure

  • std::bad_cast –

thrown by dynamic_cast when it fails with a reference type

  • std::bad_exception –

thrown when an exception type doesn't match

  • std::bad_typeid –

thrown by typeid

  • std::ios_base::failure –

thrown by functions in the iostream library

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

399

slide-44
SLIDE 44

Standard Exceptions (std::logic_error)

  • Represents “predictable” errors and errors that should have been

avoided in the first place

  • domain_error is not thrown by the standard library but may be used by third

party libraries (e.g., boost::math uses it)

  • invalid_argument is thrown if an argument is not accepted (e.g., bitset, stoX)
  • length_error is thrown when we exceed implementation defined length

limits (e.g., string, vector::reserve)

  • out_of_range is thrown by bound-checked members (e.g., vector::at)
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

400

slide-45
SLIDE 45

Standard Exceptions (std::runtime_error)

  • Represents “unpredictable” errors
  • overflow_error, underflow_error are thrown on a math overflow

(e.g., bitset, boost::math)

  • range_error is thrown when a value has an incorrect range

(e.g., wstring_convert::from_bytes)

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

401

slide-46
SLIDE 46

Summary

  • Error Handling
  • Standard Exceptions
  • Exception Specification
  • Exception Discrimination
  • Cleaning up in C++
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

402

slide-47
SLIDE 47

Advanced Software Engineering with C++ Templates

Standard Library (IO) Thomas Gschwind <thgatzurichdotibmdotcom>

slide-48
SLIDE 48

Standard Input/Output

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

404

  • Initialization
  • Exceptions
slide-49
SLIDE 49

I/O System

ios_base:

locale independent format state

basic_ios<>: locale dependent format state stream state basic_iostream<>: formatting (<<, >>, etc.) setup/cleanup basic_streambuf<>: buffering locale: format information real destination/source character buffer

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

406

slide-50
SLIDE 50

I/O Subsystem Initialization

  • We have seen previously that library initialization can be

accomplished with constructors of helper classes

  • However, the order of their construction is linker-dependent
  • The I/O Subsystem should be initialized before all other modules
  • How can we ensure its initialization independently of the link-order?
  • Ensure that every object file checks whether the I/O subsystem has been

initialized and initialize it if necessary

  • This is where anonymous namespaces are useful
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

408

class ios_base::Init { static int count; public: Init(); ~Init(); }; namespace { ios_base::Init __ioinit; } iostream

slide-51
SLIDE 51

415

Error Handling & Stream States

  • ios::good

As the name suggests, all is fine

  • ios::fail

The last operation has failed but no characters have been lost

  • ios::bad

The last operation failed, characters may have been lost

  • ios::eof

end of file

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.
slide-52
SLIDE 52

Error Handling & Stream States (cont‘d)

  • Check the stream after every input and output operation
  • Raise an exception as soon as an error occurs

template <class Ch, class Tr=char_traits<Ch> > class basic_ios: public ios_base { public:

bool good() const; bool eof() const; bool fail() const; bool bad() const;

  • perator void*() const;

bool operator!() const { return fail(); } iostate rdstate() const; void clear(iostate f=goodbit); void setstate(iostate f) { clear(rdstate()|f); } class failure; iostate exceptions() const; void exceptions(iostate except);

Stream is in state good, fail, eof, bad and shorthands for good and fail. Read and set raw iostate bits. Get and set states for which exceptions are thrown.

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

416

slide-53
SLIDE 53

Error Handling with Exceptions

  • By default errors are handled without exceptions
  • The exceptions members allow to change this

int foo(istream &is, ostream &os) { ios::iostate s=is.exceptions(); // read and store old state is.exceptions(ios::failbit); // throw exception for failbit do { try { // some really fancy code here } catch (ios_base::failure) { /* exception handling */ } } while(cin); is.exceptions(s); }

Possible source of error!

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

417

slide-54
SLIDE 54

Error Handling with Exceptions

  • Cleanup using the destructor
  • Ensures that the original exception state is restored
  • For instance, in case of an early return or an uncaught exception

class set_exceptions { ios &s; ios::iostate s_state; public: set_exceptions(ios &stream, ios::iostate e) : s(stream) { s_state = s.exceptions(); s.exceptions(e); } ~set_exceptions() { s.exceptions(s_state); } };

int foo(istream &is, ostream &os) {

set_exceptions finalizer(is, ios::failbit);

do { // some really fancy code here } while(cin);

}

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

418

slide-55
SLIDE 55

Number Converter (main)

  • Let us write a program to convert octal to hexadecimal values
  • We set most options in the main routine

#include <iostream> #include <algorithm> #include <iterator> // … set_exceptions class from previous slide, converter, etc.

int main(int argc, char *argv[]) { cin.setf(ios::oct, ios::basefield); cout.setf(ios::hex, ios::basefield); cout.setf(ios::right, ios::adjustfield); cout.fill('0'); set_exceptions finalizer(cin, ios::badbit); try { clog << converter(cin,cout) << " numbers converted"; } catch(exception) { exit(1); } }

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

419

slide-56
SLIDE 56

Number Converter (converter)

  • The converter function performs the actual conversion
  • Sets some local flags such as the output width

int converter(istream &is, ostream &os) { int i=0, n=0; set_exceptions finalizer(is, ios::failbit); do { try { while (cin >> i) { cout.width(8); cout << i << endl; ++n; /* … */ } } catch (ios_base::failure) { /* command handling such as termination */ } } while (cin); }

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

420

slide-57
SLIDE 57

Advanced Software Engineering with C++ Templates

C++ Metaprogramming Thomas Gschwind <thgatzurichdotibmdotcom>

slide-58
SLIDE 58

C++ Metaprogramming

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

422

  • With templates
  • With C++11 constexpr
  • Variadic templates
slide-59
SLIDE 59

What is Metaprogramming?

  • A computer program that has the ability to treat another program

as its data

  • Can be used to move computations from run-time to compile-time
  • Originally it was discovered that C++ templates are Turing complete
  • During the standardization of the C++ language
  • Techniques for using templates for metapgrogramming expanded
  • However, it is complex, and not intended for the majority of programs
  • Ideal, when writing specialized libraries
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

423

slide-60
SLIDE 60

Meta-Programming with Templates

  • What does this code do? Where would it be useful?

template<int P, int N> struct compute2 { static const int res=compute2<P,P%N?N-1:0>::res; }; template<int P> struct compute2<P,1> { static const int res=1; }; template<int P> struct compute2<P,0> { static const int res=0; }; template<int N> struct compute { static const int res=compute2<N,N-1>::res; }; int main() { cout << compute<3>::res << "," << compute<4>::res << "," << compute<5>::res << endl; }

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

424

slide-61
SLIDE 61

Meta-Programming with Templates

  • Yes, the code checks whether the number is a prime number

template<int P, int N> struct isprime2 { static const int res=isprime2<P,P%N?N-1:0>::res; }; template<int P> struct isprime2<P,1> { static const int res=1; }; template<int P> struct isprime2<P,0> { static const int res=0; }; template<int N> struct isprime { static const int res=isprime2<N,N-1>::res; }; int main() { cout << isprime<3>::res << "," << isprime<4>::res << "," << isprime<5>::res << endl; }

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

425

slide-62
SLIDE 62

Meta-Programming with Templates

  • Where is the previous code useful?
  • If we need somewhere a prime if we add a template to compute

the next prime

template<typename T> class my_hash_table { T table[compute_next_prime<20000>::res]; … };

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

426

slide-63
SLIDE 63

Constant Expressions [C++11]

  • Certain initializers require a constant expression
  • Size bounds of arrays, case statements
  • Functions may not be used in such constant expressions
  • Allow constexpr functions to be used for constant expressions
  • Must be marked as constexpr
  • Must be possible to evaluate them at compile time
  • More or less limited to “single” return statement [C++11]
  • Requirements for constexpr relaxed [C++14]
  • Useful in combination with static_assert

constexpr int min(int a, int b) { return a<b? a : b; } static_assert(min(consta, constb)>1, "min(consta, constb)>1"); int numbers[min(consta, constb)];

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

427

slide-64
SLIDE 64

Constant Expressions

  • Certain initializers require a constant
  • Arrays stored on the stack (e.g., char buf[256])
  • Switch expressions (same in Java)
  • Functions may not be used in such expressions (again, same in Java)
  • C++11 allows to use functions in such expressions
  • Must be marked as constexpr
  • Must be possible to evaluate them at compile time
  • Especially useful in combination with static_assert

constexpr int min(int a, int b) { return a<b? a : b; } static_assert(min(consta, constb)>1, "min(consta, constb)>1"); int numbers[min(consta, constb)];

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

428

slide-65
SLIDE 65

Meta-Programming with constexpr in C++11

  • C++11 makes our life easier (and more complicated again)
  • Meta-programming with constexpr

constexpr bool isprime2(int i, int n) { return (n%i == 0) ? false : (i*i<n) ? isprime2(i+2,n) : true; } constexpr bool isprime(int n) { return (n%2 == 0) ? (n==2) : isprime2(3,n); } constexpr int nextprime(int i) { return isprime(i) ? i : nextprime(i+1); } int main(int argc, char *argv[]) { constexpr int res = nextprime(1234567890); cout << res << endl; }

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

429

slide-66
SLIDE 66

Type Support Functions

C++11 introduced several type support functions

  • auto, decltype, declval specifiers/function
  • Allow us to identify the type of a given expression
  • These type support functions make many of the typedefs in classes (such as

vector::value_type) “obsolete”

  • Depending on the situation, these typedefs can be more readable
  • is_* functions
  • Allow us to identify the capability of a given type

(i.e., compile-time introspection)

  • Allows to verify as part of our template that the requirements for our

template parameters are met

  • Allows to tweak our template implementation based on a type’s capability
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

430

slide-67
SLIDE 67

New auto “Data Type” [C++11]

  • Compiler will infer the type from the assigned expression
  • Type must be uniquely determined during compile-time
  • Makes C++ programs easier to write and read (if used correctly)

int i = 3; auto j = i; // evaluated to int string s("Hello World"); auto k = cond()? i : s; // ambiguous: int or string? auto it = v.begin(); // evaluated to typename vector<int>::iterator vector<int> v = some_function();

Could have used auto as well but possibly not more readable.

  • If the return type of some_function() is not obvious, using vector<int>

may make the code clearer.

  • If the code needs a vector<int>, vector<int> will make the compiler error

easier to understand, if the return type of the function ever changes.

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

431

slide-68
SLIDE 68

New auto “Data Type” (cont’d)

  • Can make code easier to maintain
  • Automatic deduction of return type with auto [C++14]
  • First return statement must allow to determine return type
  • Subsequent return statements must return the same type

void printMaxWidth(const vector<Shape> &shapes) { auto max_width = shapes.front().getWidth(); for (const auto &s : shapes) { auto w = s.getWidth(); if (w > max_width) max_width = w; } cout << "max width is " << max_width << endl; }

Thanks to auto the representation of a shape’s width may change but we do not need to update printMaxWidth.

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

432

auto factorial(int n) { if (n <= 1) return 1; return factorial(n-1)*n; }

slide-69
SLIDE 69

decltype(e) [C++11]

  • Returns the type of the expression e
  • If the type we want to use depends on an expression
  • Could have looked up the type returned by getWidth() but

would not work well if the Shape type itself were generic

  • Unlike auto, decltype does not strip references
  • If getWidth() returns a reference, the initialization would yield a compile

time error since a reference cannot be initialized with an rvalue

  • Use remove_reference<decltype(…)>::type sum_width;
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

433

void printTotalWidth(const vector<Shape> &shapes) { decltype(shapes.front().getWidth()) sum_width; for (const auto &s : shapes) { sum_width += s.getWidth(); } cout << "width is " << sum_width << endl; }

slide-70
SLIDE 70

declval<t> [C++11]

  • Returns a reference to an object of type t
  • Since the object is never instantiated, it can only be used in decltype

expressions

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

434

template<class S> class Aggregator { decltype(declval<S>().getWidth()) sum_width; … public: Aggregator(…) : sum_width(0), … { … } void add(…, const S &s) { sum_width += s.getWidth(); … } }

Again, decltype does not strip references

  • If getWidth() returns a reference, this would yield a compile time error
  • Use remove_reference<decltype(…)>::type sum_width = 0;
slide-71
SLIDE 71

Querying a type’s capabilities

  • C++11 provides many functions to query a type for its capabilities

(each implemented as template)

  • is_array, is_enum, is_function, is_base_of, …
  • is_standard_layout, is_pod, …
  • is_copy_assignable, …
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

435

slide-72
SLIDE 72

Type Support: Asserting Capabilities

  • Most (all?) our RPN calculator at some point in time create a copy
  • f the type it is calculating with
  • Hence, the type must be copy_assignable (is_copy_assignable)
  • Depending on the implementation possibly also copy_constructible (is_...)
  • One use of this is to generate easy to understand error messages
  • Typically, type errors, missing functions can only be identified at the point

the corresponding artifact is used

  • With templates this may depend on a template instantiation
  • Which in turn may again depend on a template instantiation
  • And so on and so on…
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

436

slide-73
SLIDE 73

Type Support: rpn_calculator

  • The static assertion in combination with the error messages gives

the developer an easier to understand message of what is going on

  • We also get the extended error when that code is instantiated

(useful if we need to know the gory details)

#include <type_traits> template<typename T> class rpn_calculator { static_assert(is_copy_assignable<T>::value,

"rpn_calculator's number type must be a copy assignable type");

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

437

slide-74
SLIDE 74

Type Support: Adapt Behavior to Generic Type

  • Implement function differently depending on its underlying

type/characteristics

  • This is similar to the distance implementation we have seen previously
  • Example: Copy a series of elements
  • If a type is a plain old data type, it can be copied with memcpy(…)
  • Otherwise, it needs to be copied explicitly by assignment (operator=(…))
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

438

slide-75
SLIDE 75

Type Support: Copying Memory Efficiently

  • The std::enable_if<B,T> function evaluates to a type

with type=T if B==true, otherwise to an empty type

  • An empty type leads to an invalid template substitution and is ignored

according to the “Subsitution Failure Is Not An Error” rule (SFINAE)

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

439

template<bool B, typename T=void> using EnableIf = typename std::enable_if<B,T>::type; template<typename T> EnableIf<std::is_pod<T>::value> copy(const T *src, T *dst, unsigned len) { memcpy(dst, src, len*sizeof(T)); } template<typename T> EnableIf<!std::is_pod<T>::value> copy(const T *src, T *dst, unsigned len) { for (unsigned i = 0; i < len; ++i) *dst++ = *src++; } EnableIf here may look

  • bizarre. SFINAE is intended

for a different purpose.

slide-76
SLIDE 76

SFINAE: Substitution Failure is Not An Error

  • When an error occurs, typically the compiler stops evaluating the

expression and issues an error

  • However, sometimes this is not desirable (e.g., overloading)
  • If we invoke a function get_it(…) the compiler collects all get_it() functions

that may be used and selects the most appropriate one for its arguments

  • If get_it is generic and any of the template parameters do not match for one
  • f the many overloads, do we want the compiler to stop?
  • Yes, if no useful get_it function can be found (but that was not the question)
  • Actually, no as long as there are candidates that should be invoked
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

440

slide-77
SLIDE 77

SFINAE: Substitution Failure is Not An Error (cont’d)

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

441

struct foo { int x; }; struct bar { double y; }; template<typename T> auto get_it(T t) -> decltype(t.x) { return t.x; } template<typename T> auto get_it(T t) -> decltype(t.y) { return t.y; } int main(…) { get_it(foo()); // ok, first overload is valid and matches get_it(bar()); // ok, second overload is valid and matches get_it(5); // error, all overloads were discarded }

slide-78
SLIDE 78

Concepts [C++TBD]

  • Concepts are a further extension to the type support functions
  • Express not only compiler related type attributes but concepts such as is_ordered,

etc.

  • This allows us also to specify is_ordered as a requirement for our rpn_calculator as the min

function depends on it.

  • However, this idea is already around since C++0x (which became C++11)
  • They did not make it into C++11, or C++14, and not C++17 either
  • To play with concepts we have to revert to the BOOST Concept Library
  • Good news this library even works with old C++03 code 

#include <type_traits> #include <boost/concept_check.hpp> template<typename T> class rpn_calculator { static_assert(is_copy_assignable<T>::value,

"rpn_calculator's number type must be a copy assignable type");

BOOST_CONCEPT_ASSERT((boost::LessThanComparable<T>)); …

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

442

slide-79
SLIDE 79

L6.1: Locker Class

  • Implement a small class such as the DBLocker class
  • Instead, it is called FileLocker to lock a file
  • You do not actually have to lock the file, it is sufficient if you print to

standard out that the file is now “locked”, respectively “unlocked

  • If you really want to lock the file, see man 2 flock on Unix(-like) systems
  • Use that class in your RPN calculator or pvector class for the file of

the persistent vector

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

443

slide-80
SLIDE 80

L6.2: Metaprogramming

  • Implement two template meta-programs similar to the isprime

template meta-program

  • One that computes the Greatest Common Denominator

(using the Euclidean algorithm)

  • One that computes the n-th Fibonacci number

(starting with fib(0) = 0, fib(1) = 1)

  • Reimplement both functions using C++11 constexpr.
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

444

slide-81
SLIDE 81

L6.3: Reimplement distance with enable_if

  • Reimplement our distance function that used dynamic algorithm

selection with the enable_if feature we have seen in this lecture

  • Hint: If you use the using directives from a previous slide life gets easier and

the code more readable

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

445

slide-82
SLIDE 82

446

L6.4 RPN Calculator with Concepts (Optional)

  • Words of Caution
  • The BOOST concept classes and enable_if don’t work well together which

is why this exercise is optional

  • Only if you like tinkering around and you are curious, this is for you
  • Extend the RPN calculator such that the min function is available

for ordered types but unavailable for unordered types (i.e., complex numbers).

  • Th. Gschwind. Advanced Software Engineering with C++ Templates.
slide-83
SLIDE 83
  • Th. Gschwind. Advanced Software Engineering with C++ Templates.

447

Next Lecture

  • Initializing Your Library

(Lessons learned from standard IO)

  • Stream Buffers
  • Expression Templates

Have a nice weekend! Have fun solving the examples! See you next week!