Department I C Plus Modern and Lucid C

  • Slides: 29
Download presentation
Department I - C Plus Modern and Lucid C++ Advanced for Professional Programmers Part

Department I - C Plus Modern and Lucid C++ Advanced for Professional Programmers Part 8 b – Testat 2 Background Thomas Corbat / Prof. Peter Sommerlad Rapperswil, 23. 02. 2017 HS 2017

Topics n Handling Non-Default-Constructible Types in Your Bounded Buffer n Implementing Special Member Functions

Topics n Handling Non-Default-Constructible Types in Your Bounded Buffer n Implementing Special Member Functions n Iterator for Bounded. Buffer 2

Non-Default-Constructible Types

Non-Default-Constructible Types

Non-Default-Constructible Types n A type is non-default-constructible when. . . there is no explicit

Non-Default-Constructible Types n A type is non-default-constructible when. . . there is no explicit default constructor and. . . n. . . there is no implicit default constructor! n Why can't we use new T[capacity] for allocating a T array? n If T is of class type it must be default-constructible to allow this call. n That is not the case for fundamental types! n Alternative n Allocate a char array and manage the objects "manually" 4

Char Array as Memory for Elements (Allocation) n How to declare the member variable?

Char Array as Memory for Elements (Allocation) n How to declare the member variable? n As plain char pointer char * values_memory; n As std: : unique_ptr<char[]> values_memory; n Allocating a char array of correct size Buffer(size_t capacity) : values_memory{new char[sizeof(T) * capacity]}{} n sizeof returns the size in byte (or char since sizeof(char) is always 1) n Invalid (In T[capacity] must not have dynamic capacity – C 99 Extension) Buffer(size_t capacity) : values_memory{new char[sizeof(T[capacity])]}{} 5

Important 6 values[0] : <empty> n The array containing the data is just plain

Important 6 values[0] : <empty> n The array containing the data is just plain memory (zeros and ones) n Lifetime values_memory size: 3 start_index: 1 push n Access to this memory has to be handled with care! Buffer values_memory size: 4 start_index: 1 n Destruct element n Construction and destruction is independent of memory allocation and deallocation! values[3] : T values[4] : <empty> values[1] : T values[2] : T values[3] : T values[4] : T pop n Access element values[2] : T values[0] : <empty> Buffer n Construct element in place values[1] : T values[0] : <empty> Buffer values_memory size: 3 start_index: 2 values[1] : <empty> values[2] : T values[3] : T values[4] : T

Char Array as Memory for Elements (Deallocation) How can we deallocate such a char

Char Array as Memory for Elements (Deallocation) How can we deallocate such a char array. . . n. . . when using the plain pointer? char * values_memory; n Destructor ~Buffer() { delete[] values_memory; } n Assignment operators B & operator=(B const & o) { delete[] values_memory; values_memory = new char[. . . ]. . . return *this; } n. . . when using the unique_ptr? std: : unique_ptr<char[]> values_memory; n Destructor ~Buffer() { //unique_ptr gets deallocated implicitly } n Assignment operators B & operator=(B const & o) { values_memory. reset(new char[. . . ]). . . return *this; } Is that everything we need? 7

Char Array as Memory for Elements (Deallocation) 8 n Deleting the char array does

Char Array as Memory for Elements (Deallocation) 8 n Deleting the char array does nothing except releasing the allocated memory n That is good, since it does not call any destructors! n That is bad, for the same reason! n What if the element type T manages resources itself? Buffer values_memory values[0] : T values[1] : T values[2] : T delete[] values_memory; resource Buffer resource values_memory n We need to properly destruct the elements in the array! Otherwise we (might) end up with memory leaks!

Char Array as Memory for Elements (Deallocation) n The buffer has to be empty

Char Array as Memory for Elements (Deallocation) n The buffer has to be empty before deallocating the array! void clear() { while(!empty()) { pop(); } } n Calling clear before deleting the array (or resetting the unique_ptr) solves the problem, as long as pop() works correctly. . . n. . . by calling the destructor of the popped element! void pop() { //check not empty front(). ~T(); //adjust indices/size } 9

Pushing Elements into the Buffer 10 n What we did in push() so far

Pushing Elements into the Buffer 10 n What we did in push() so far void push(T const & e) { //check not full values_memory[insert. Index()] = e; //adjust indices/size } n Example: T = int (with sizeof(T) = 4) and capacity = 2 char values_memory{new char[2 * sizeof(int)]} int

Pushing Elements into the Buffer n Pushing the value 1 to index 0 values_memory[0]

Pushing Elements into the Buffer n Pushing the value 1 to index 0 values_memory[0] = 1; 11 char 10 00 00 00 int n Pushing the value 2 to index 1 values_memory[1] = 2; char 10 00 01 00 00 00 int n Because values_memory is of type char *, the value gets trimmed (narrow converted)

Pushing Elements into the Buffer n Pushing the value 1 to index 0 *reinterpret_cast<int*>(values_memory

Pushing Elements into the Buffer n Pushing the value 1 to index 0 *reinterpret_cast<int*>(values_memory + 0) = 1; 12 char 10 00 00 00 00 int n Pushing the value 2 to index 1 *reinterpret_cast<int*>(values_memory + 1) = 2; char 10 00 01 00 00 00 00 int n Because values_memory is of type char *, array index access is not properly aligned with T (int)

Pushing Elements into the Buffer 13 n A helper function can help with the

Pushing Elements into the Buffer 13 n A helper function can help with the problem T * elements() const { return reinterpret_cast<T *>(values_memory); } n Pushing the value 2 to index 1 elements()[1] = 2; char 10 00 00 00 int n Everything is awesome! Right. . . ? 00 00 00 01 00 00 00 00

Pushing Elements into the Buffer 14 n What about non-trivial types? void push(T const

Pushing Elements into the Buffer 14 n What about non-trivial types? void push(T const & e) { //check not full elements()[insert. Index()] = e; //adjust indices/size } n What happens in the assignment? memory space for #capacity T objects Buffer § values_memory content: ? ? ? Call of assignment operator on "? ? ? "! Accessing Uninitialized Memory

Pushing Elements into the Buffer n We have to create elements in place! void

Pushing Elements into the Buffer n We have to create elements in place! void push(T const & e) { //check not full new(elements() + insert. Index()) T{e}; //adjust indices/size } n Placement new with argument of type T. . . n. . . does NOT allocate new memory. . . n. . . and calls the copy constructor to copy-initialize the object in place n Anything else? n copy-assignment/construction? n move-assignment/construction? 15

Question n Given: An array of elements of type T is accessible through a

Question n Given: An array of elements of type T is accessible through a pointer to char. How to correctly access the fifth element in this array. Declaration of arr: char * arr; a) arr[4] b) reinterpret_cast<T*>(arr)[4] c) *reinterpret_cast<T*>(arr + 4) d) *reinterpret_cast<T*>(arr) + 4 16

Special Member Functions

Special Member Functions

Constructors 18 n No default constructor n Size constructor n Exception on size 0

Constructors 18 n No default constructor n Size constructor n Exception on size 0 explicit Bounded. Buffer(size_type capacity) : start. Index { 0 }, n. Of. Elements { 0 }, buffer. Capacity { capacity }, values_memory { create. Elements. Memory(capacity) } { if (capacity == 0) { throw std: : invalid_argument { "Buffer size must be > 0. " }; } }

Copy Constructor n Initialize empty Bounded. Buffer(Bounded. Buffer const & other) : start. Index

Copy Constructor n Initialize empty Bounded. Buffer(Bounded. Buffer const & other) : start. Index { 0 }, n. Of. Elements { 0 }, buffer. Capacity { other. buffer. Capacity }, values_memory { create. Elements. Memory(buffer. Capacity) } { copy. Elements(other); } n Copy elements void copy. Elements(Bounded. Buffer const & other) { std: : for_each( std: : begin(other), std: : end(other), [this](auto const & element) { this->push(element); } 19

Move Constructor 20 n Initialize empty n swap n Takes the content of the

Move Constructor 20 n Initialize empty n swap n Takes the content of the other Bounded. Buffer n Leaves it in valid (destructable) state Bounded. Buffer(Bounded. Buffer && other) noexcept : start. Index {0}, n. Of. Elements {0}, buffer. Capacity {0}, values_memory {nullptr} { swap(other); }

Copy Assignment n Copying single elements might fail n If that happens in the

Copy Assignment n Copying single elements might fail n If that happens in the middle of the copy operation our this object is in half-modified state n Copy-Swap Idiom n Creating a copy might fail n Swap should not fail n If we create a copy first and then swap with that copy it is an all-or-nothing operation Bounded. Buffer & operator=(Bounded. Buffer const & other) { if (this != &other) { //Might be omitted (performance) Bounded. Buffer copy {other}; swap(copy); } return *this; } 21

Move Assignment 22 n Just swap both buffers n The other Bounded. Buffer will

Move Assignment 22 n Just swap both buffers n The other Bounded. Buffer will be destroyed soon (it is an rvalue) n No need to explicitly clean up this object before Bounded. Buffer & operator=(Bounded. Buffer && other) { swap(other); return *this; } n Depending on the members used you must prevent self-swap Bounded. Buffer & operator=(Bounded. Buffer && other) { if (this != &other) { swap(other); } return *this; }

Smart Combined Assignment 23 n Value overload binds to lvalues and rvalues n Results

Smart Combined Assignment 23 n Value overload binds to lvalues and rvalues n Results in copy or move initialization of parameter n Move is very cheap BB & operator=(BB const & other) { BB copy {other}; swap(copy); return *this; } BB & operator=(BB && other) { swap(other); return *this; } Bounded. Buffer & operator=(Bounded. Buffer other) { swap(other); return *this; }

Destructor 24 n Elements have to be destroyed explicitly n delete of char *

Destructor 24 n Elements have to be destroyed explicitly n delete of char * will not call destructor for T ~Bounded. Buffer() { pop. All. Elements(); } n Get rid of the remaining elements void pop. All. Elements() { while(!empty()) { pop(); } }

Iterator for Bounded. Buffer

Iterator for Bounded. Buffer

Iterator Base Template n Member template of Bounded. Buffer for access to private parts

Iterator Base Template n Member template of Bounded. Buffer for access to private parts n Required data n Position (relative from beginning) n Flag to mark end iterator n Reference/pointer to Bounded. Buffer 26

Iterator Base Template API template<typename Iter> struct Buffer. Iterator. Base : public boost: :

Iterator Base Template API template<typename Iter> struct Buffer. Iterator. Base : public boost: : random_access_iterator_helper<Iter, value_type> { using difference_type = typename std: : iterator_traits<Buffer. Iterator. Base<Iter>>: : difference_type; Buffer. Iterator. Base(Bounded. Buffer const & buffer, size_type index, bool at_end = false); const_reference operator*() const; bool operator ==(Buffer. Iterator. Base const & other) const; bool operator <(Buffer. Iterator. Base const & other) const; difference_type operator-(Buffer. Iterator. Base const & other) const; friend } Iter& operator++(& i); operator--(IIterter & i); operator+=(Iter & i, difference_type diff); operator-=(Iter& i, difference_type diff); 27

Subtypes for Const and Non-Const Iterator n Const iterator struct Buffer. Const. Iterator: Buffer.

Subtypes for Const and Non-Const Iterator n Const iterator struct Buffer. Const. Iterator: Buffer. Iterator. Base<Buffer. Const. Iterator> { using Buffer. Iterator. Base<Buffer. Const. Iterator>: : Buffer. Iterator. Base; }; n Non-const iterator n Requires operator* that returns reference struct Buffer. Iterator: Buffer. Iterator. Base<Buffer. Iterator> { using Buffer. Iterator. Base<Buffer. Iterator>: : Buffer. Iterator. Base; reference operator*() const { this->check. Not. At. End(); return this->buffer. elements()[this->index]; } }; 28

Summary n You must not access uninitialized objects n Create objects in place in

Summary n You must not access uninitialized objects n Create objects in place in allocated memory n Destroy unmanaged objects properly n Release allocated memory properly 29