Last Class Template metaprogramming and typetraits Multiple inheritance

  • Slides: 53
Download presentation
Last Class • Template meta-programming and type-traits • Multiple inheritance • Diamond of dread

Last Class • Template meta-programming and type-traits • Multiple inheritance • Diamond of dread ! • Virtual base class • C-tors order • Exceptions • throw(), try, catch, etc… • Inheritance • stack unwinding • terminate() • RTTI • Polymorphic type 1

Throw Temporary Object, Catch by Value //Compilation error: void f(T& t) {} f(T()); //No

Throw Temporary Object, Catch by Value //Compilation error: void f(T& t) {} f(T()); //No compilation error: try { throw T(); } catch (T& t) { } 2 Special feature of • exceptions mechanism, that the thrown object is guaranteed to have a lifetime of the exception handling. Implemented differently • on different compilers

c++ style casting �static_cast<type>(expression) �const_cast<type>(expression) �reinterpret_cast<type>(expression) �dynamic_cast<type>(expression) 3

c++ style casting �static_cast<type>(expression) �const_cast<type>(expression) �reinterpret_cast<type>(expression) �dynamic_cast<type>(expression) 3

‘static_cast’ and ‘dynamic_cast’ int i = static_cast<int>(12. 45); Shape* s = container. pop(); Circle*

‘static_cast’ and ‘dynamic_cast’ int i = static_cast<int>(12. 45); Shape* s = container. pop(); Circle* c = dynamic_cast<Circle*>(s); 4

Important point about exceptions struct B { B() {cout << "in B's c-tor" <<

Important point about exceptions struct B { B() {cout << "in B's c-tor" << endl; } ~B() {cout << "in B's d-tor" << endl; } }; struct A : public B { A() {cout << "in A's c-tor" << endl; } ~A() {cout << "in A's d-tor" << endl; } }; void main() { try { A* p=new A; throw(2); } catch (. . . ) { } } 5 Oh ! NO

Solutions • set_terminate(Take. Care. Of. Everything) • catch(…) • maybe there are more creative

Solutions • set_terminate(Take. Care. Of. Everything) • catch(…) • maybe there are more creative ideas • auto_ptr (smart pointers) 6

Smart Pointers

Smart Pointers

Memory Management • One of the major issues in writing C/C++ code is managing

Memory Management • One of the major issues in writing C/C++ code is managing dynamically allocated memory • Biggest question is how to ensure that allocated memory will be freed when it is no longer in use • Also important – allocate and free efficiently, in space (fragmentation) and time (new is expensive). [out of our scope] 8

Strategies to memory management 1) fixed ownership 2) dynamic ownership compile 2) run time

Strategies to memory management 1) fixed ownership 2) dynamic ownership compile 2) run time 1) 3) reference counting • 9 non reference counting garbage collection (used in. NET) will not be discussed

Strategy #1: Fixed Ownership • Allocated pointer belongs to the entity (function or objects)

Strategy #1: Fixed Ownership • Allocated pointer belongs to the entity (function or objects) that created it • That entity is responsible for freeing the pointer void foo() { char* mem = new char[1000]; . . . delete [] mem; } 10

Example: String class String { public: String( char const *str ) { int l

Example: String class String { public: String( char const *str ) { int l = strlen(str); _data = new char[l]; memcpy(_data, str, l); } ~String() { delete [] _data; } private: char* _data; 11 }; • The memory for char* represented by String is owned by the object • Class encapsulates details of memory management • With careful implementation, this class is memory-tight

Strategy #2: Dynamic ownership (compile time) • Object has an owner • However, owner

Strategy #2: Dynamic ownership (compile time) • Object has an owner • However, owner changes through specific function calls Example: strdup(s) allocates new memory and returns a pointer to it in the return value, strdup does not release the memory #include <string. h> char *strdup(const char *s); Very hard to keep track of responsibility. 12

Ownership transfer and interface design Design principle: The allocator of a resource is responsible

Ownership transfer and interface design Design principle: The allocator of a resource is responsible to free it. • Reduces memory leaks due to interface problems 13

Example: design choices Read. Line – read a line of input All of the

Example: design choices Read. Line – read a line of input All of the above options are problematic! char* Read. Line() – returns pointer to newly allocated memory – user has to free it char* Read. Line() – returns pointer to static buffer – user has to copy line to another buffer void Read. Line(char* Buffer, int len) – uses a user supplied buffer – Read. Line cannot resize buffer 14

Strategy #3: Dynamic ownership (run time) • In some cases we cannot know who

Strategy #3: Dynamic ownership (run time) • In some cases we cannot know who will own a pointer • In other cases, we do not want the programmer to remember who owns what • Can we use an object that “remembers” who owns the object? 15

“Dumb” Pointers Suppose T is a class T* is a “dumb pointer” supplied by

“Dumb” Pointers Suppose T is a class T* is a “dumb pointer” supplied by compiler It remembers the address of the object and allows us to access it, but nothing else. Can we build smarter pointers, that will be able to do more than that? • Memory clean-up • Proxy • … 16

The Pointer Interface To implement a pointer-like object we need (approximately): pointer: : pointer(

The Pointer Interface To implement a pointer-like object we need (approximately): pointer: : pointer( T*) pointer: : pointer( pointer const& ) pointer& pointer: : operator=( pointer const& ) T& pointer: : operator*() T* pointer: : operator->() If it looks like a pointer and feels like a pointer… 17

“Smart” Pointers The STL contains two smart pointers: 1. auto_ptr. It warps a pointer,

“Smart” Pointers The STL contains two smart pointers: 1. auto_ptr. It warps a pointer, and when destructed, frees the pointed memory. Copying is ownership transfer - only one auto_ptr points to each memory location. Depricated in c++11. 2. unique_ptr – replaces auto_ptr in c++11 3. shared_ptr – "reference counting" pointer. multiple pointers can point to one location, when the last one is destructed the memory is freed. Introduced in tr 1 and c++11 added functionality 18

auto_ptr #include <memory> struct bar { }; void foo() { auto_ptr<bar> my. Bar(new bar);

auto_ptr #include <memory> struct bar { }; void foo() { auto_ptr<bar> my. Bar(new bar); // use my. Bar as a pointer } // my. Bar is deleted automatically 19

Auto_ptr in exceptions Auto_ptrs provide clean mechanism to deal with exception void foo() {

Auto_ptr in exceptions Auto_ptrs provide clean mechanism to deal with exception void foo() { try { auto_ptr<bar> my. Bar = new bar; apple(); //apple might throw an execption } catch (exception& e) {} } // my. Bar is deleted automatically // even if an exception is thrown by apple 20

Auto_ptr auto_ptr is like a pointer, but not exactly: void foo() { auto_ptr<bar> my.

Auto_ptr auto_ptr is like a pointer, but not exactly: void foo() { auto_ptr<bar> my. Bar(new bar); if(true) { auto_ptr<bar> my. Other. Bar = my. Bar; } } //calls dectrutors of my. Bar, my. Other. Bar What happens in this example? 21

Transfer of Ownership To avoid multiple deletes, auto_ptr ensures that: only one auto_ptr points

Transfer of Ownership To avoid multiple deletes, auto_ptr ensures that: only one auto_ptr points to a certain object The statement auto_ptr<bar> my. Other. Bar = my. Bar; transfer the ownership from my. Bar to my. Other. Bar How do we achieve this? 22

auto_ptr details 23 No const? suspicious… auto_ptr( auto_ptr& rhs ) : _ptr( rhs. release()

auto_ptr details 23 No const? suspicious… auto_ptr( auto_ptr& rhs ) : _ptr( rhs. release() ) { } auto_ptr& operator=( auto_ptr& rhs ) { reset( rhs. release() ); return *this; Why do we do this? } What does it remind us? void reset(T* p = NULL) { if( _ptr != p ) delete _ptr; _ptr = p; } T* release() { T* tmp = _ptr; _ptr = NULL; return tmp; }

auto_ptr and stack unwinding auto_ptr is basically a wrap to a pointer, so that

auto_ptr and stack unwinding auto_ptr is basically a wrap to a pointer, so that it is automatically deleted when we leave the scope. My. Class* f(. . . ) { auto_ptr<My. Class> p(new My. Class()); . . . return p. release(); } If an exception occurs prior to return, and is caught, the instance of My. Class will be automatically deleted by p’s destructor 24

auto_ptr summary 1. Provide mechanism for transfer of ownerships 2. Does not replace the

auto_ptr summary 1. Provide mechanism for transfer of ownerships 2. Does not replace the need for pointers (Only one auto_ptr can point a resource at a time) 3. Can be dangerous - Copy ctor and op. = are disguised move! 2. DO NOT use in STL containers (because of the unusual copying behavior) 1. 4. Further reading: http: //msdn. microsoft. com/enus/library/hh 279674. aspx http: //www. cprogramming. com/tutorial/auto_ptr. html 25

auto_ptr => unique_ptr • auto_ptr is depricated • replaced by unique_ptr in c++11 –

auto_ptr => unique_ptr • auto_ptr is depricated • replaced by unique_ptr in c++11 – gives ownership errors in compile time (no operator=) • Can be used in stl containers • Uses move semantics and rvalue reference (hopefully we will talk about it next week) • You can read more at: http: //www. informit. com/guides/content. aspx? g=cplus&seq. Num=400 26

unique_ptr Clearer syntax: auto_ptr: std: : auto_ptr<int> p(new int); std: : auto_ptr<int> p 2

unique_ptr Clearer syntax: auto_ptr: std: : auto_ptr<int> p(new int); std: : auto_ptr<int> p 2 = p; unique_ptr: std: : unique_ptr<int> p(new int); std: : unique_ptr<int> p 2 = std: : move(p); 27

unique_ptr in stl containers Can be used in containers: vector<unique_ptr<int> > v; unique_ptr<int> p(new

unique_ptr in stl containers Can be used in containers: vector<unique_ptr<int> > v; unique_ptr<int> p(new int); v. push_back(move(p)) 28

Strategy #4: Reference Counting • In some cases, we do not want to establish

Strategy #4: Reference Counting • In some cases, we do not want to establish ownership. • Managing ownership within forces us to make unnecessary copies. • Can we get away with memory management without ownership? • The general mechanism of reference counting provides one solution 29

std: : c++11: : shared_ptr • Uses "shared memory semantics" – (i. e. ,

std: : c++11: : shared_ptr • Uses "shared memory semantics" – (i. e. , in most cases – reference counting). • In addition to pointing to the object all instances keep a pointer to a counter. • The counter is: • Increased on creation • Increased on copy • Decreased on delete • When counter reaches 0 the destructor is called. 30

std: : c++11: : shared_ptr Very simplistic shared pointer: template <typename T> class SPtr

std: : c++11: : shared_ptr Very simplistic shared pointer: template <typename T> class SPtr { private: int* _cnt; T* _p; Not complete and not precise public: T operator*() { return *_p; } const T& operator*() const; { return *_p; } SPtr(const T*& p): _p(p), _cnt(new int(1)) {} SPtr(const SPtr& o) {*this=o; } 31 SPtr operator=(const SPtr& o) { _p=o. _p; _cnt=o. _cnt; (*_cnt)++; } ~SPtr() { (*_ cnt)--; if ((*_ cnt)==0) { delete _p; delete _cnt; } } //. . . }

std: : c++11: : shared_ptr SPtr(const T*& p): _p(p), _cnt(new int(1)) {} SPtr(const SPtr&

std: : c++11: : shared_ptr SPtr(const T*& p): _p(p), _cnt(new int(1)) {} SPtr(const SPtr& o) {*this=o; } SPtr operator=( const SPtr& o) { _p=o. _p; _cnt=o. _cnt; (*_cnt)++; } 32 ~SPtr() { (*_ cnt)--; if ((*_ cnt)==0) { delete _p; delete _cnt; } } Not complete and not precise

Using shared_ptr int main () { shared_ptr<Dog> p. D(new Dog("Gunner")); //count=1 { shared_ptr<Dog> p.

Using shared_ptr int main () { shared_ptr<Dog> p. D(new Dog("Gunner")); //count=1 { shared_ptr<Dog> p. D 2 = pd; //count = 2 } //count = 1 } //count = 0 34

shared_ptr Do not initialize 2 shared ptrs from the same raw pointer: Dog* p

shared_ptr Do not initialize 2 shared ptrs from the same raw pointer: Dog* p = new Dog(); shared_ptr<int> p 1(p); // count = 1 shared_ptr<int> p 2=p 1; // count = 2 But still: delete p; //will cause double delete 35

Memory wasting std: : string a = "foo bar"; std: : string b =

Memory wasting std: : string a = "foo bar"; std: : string b = a; std: : string c = a; The resulting memory structure: a: foo bar b: foo bar c: foo bar stack 36 heap

General Reference Counting Space can be preserved if we use reference counting : a:

General Reference Counting Space can be preserved if we use reference counting : a: b: c: 37 3 foo bar

Copy On Write (COW) What happens when we change one of the objects? a

Copy On Write (COW) What happens when we change one of the objects? a = "foo bar 2"; All the objects change Solution: “Copy on Write” (COW) – make a copy of an object when it is modified - “lazy evaluation”. a: b: c: 38 3 foo bar

COW is actually an optimization technique • example: std: : string x("Hello"); 39 std:

COW is actually an optimization technique • example: std: : string x("Hello"); 39 std: : string y = x; // x and y use the same buffer y += ", World!"; // now y uses a different buffer // x still uses the same old buffer

When to use what ? auto_ptr/unique_ptr Ownership transfer: �simple yet problematic �Good for local

When to use what ? auto_ptr/unique_ptr Ownership transfer: �simple yet problematic �Good for local variables �DON’T put (auto_ptr) in STL containers! shared_ptr (reference counted): �Simple, memory safe, efficient+�Very good for large objects, and STL containers 40

More about smart pointers Many useful libraries use smart pointers for their memory management:

More about smart pointers Many useful libraries use smart pointers for their memory management: �Boost (integrated later c++11): �http: //www. boost. org/libs/smart_ptr. htm �From wikipedia: �http: //en. wikipedia. org/wiki/Smart_pointer 41

int main() { Wrapper<int> pass_grade(55); Wrapper<int> excellent_grade(100); Wrapper<int> super_grade(100); cout << (pass_grade == excellent_grade)

int main() { Wrapper<int> pass_grade(55); Wrapper<int> excellent_grade(100); Wrapper<int> super_grade(100); cout << (pass_grade == excellent_grade) << " "; pass_grade=pass_grade; Wrapper<int> best_grade(excellent_grade); cout << (best_grade == excellent_grade) << " "; best_grade=super_grade; cout << (super_grade==best_grade) << " "; excellent_grade=95; cout << (best_grade == excellent_grade) << " "; Wrapper<int> another_grade(pass_grade); another_grade=excellent_grade; cout << (another_grade == excellent_grade) << " "; cout << (another_grade == 95) << edl; return 0; } 43 member- אילו נדרשות functions למחלקה הטמפלייטית בכדי שתוכנית Wrapper הנ"ל תעבור main- ה ? קומפילציה

 נדרשות למחלקה הטמפלייטית member-functions אילו ? הנ"ל תעבור קומפילציה main- בכדי שתוכנית ה

נדרשות למחלקה הטמפלייטית member-functions אילו ? הנ"ל תעבור קומפילציה main- בכדי שתוכנית ה Wrapper(const T& x) Wrapper(Wrapper& other) Wrapper& operator=(const T& data) bool operator==(const Wrapper& other. Data) 44

template <class T> class Wrapper { public: Wrapper(const T& x): _p(new T(x)), _counter(new int(1))

template <class T> class Wrapper { public: Wrapper(const T& x): _p(new T(x)), _counter(new int(1)) {} Wrapper(Wrapper& other) { _p=other. _p; _counter=other. _counter; (*_counter)++; } ~Wrapper() { (*_counter)--; if (*_counter==0) { delete _p; delete _counter; } } 46

47 Wrapper& operator=(Wrapper& other) { if (this != &other) { (*_counter)--; if (*_counter==0) {

47 Wrapper& operator=(Wrapper& other) { if (this != &other) { (*_counter)--; if (*_counter==0) { delete _p; delete _counter; } _p=other. _p; _counter=other. _counter; (*_counter)++; } return *this; } Wrapper& operator=(const T& data) { if (*_p!=data) { (*_counter)--; if (*_counter==0) delete _p; delete _counter; _p=new T(data); _counter=new int(1); } return *this; }

bool operator==(const Wrapper& other) { return (*_p==*other. _p); } bool operator==(const T& other. Data)

bool operator==(const Wrapper& other) { return (*_p==*other. _p); } bool operator==(const T& other. Data) { return (*_p==other. Data); } private: T* _p; int* _counter; }; 48

few small things 50

few small things 50

mutable • mutable means that a variable can be changed by a const function

mutable • mutable means that a variable can be changed by a const function (even if the object is const) • Can be applied only to non-static and non-const data members of a class X { public: X(): m_flag(true){} bool Get. Flag() const { m_access. Count++; return m_flag; } private: bool m_flag; mutable int m_access. Count; }; int main() { const X x; x. Get. Flag(); } 51

Pointer to member function struct X { virtual int& Val 1() { return _val

Pointer to member function struct X { virtual int& Val 1() { return _val 1; } int& Val 2() { return _val 2; } int _val 1, _val 2; X(): _val 1(1), _val 2(2){} }; int main() { X x; cout << x. _val 1 << " " << x. _val 2 << endl; Callback(x, &X: : Val 1, 5); cout << x. _val 1 << " " << x. _val 2 << endl; template <class T> void Callback(X& x, T& (X: : *func)(), T val) { T a=(x. *func)(); cout << "In callback first: a=" << a << endl; Callback(x, &X: : Val 2, 7); cout << x. _val 1 << " " << x. _val 2 << endl; } (x. *func)()=val; a=(x. *func)(); cout << "In callback second: a=" << a << endl; } 52

Method hiding struct B { (virtual) void f(bool i) {cout << "bool" << endl;

Method hiding struct B { (virtual) void f(bool i) {cout << "bool" << endl; } }; struct D : public B { void f(int b) {cout << "int" << endl; } }; int main(){ D d; d. f(true); d. B: : f(true); d. f(3); d. B: : f(3); } 53

Polymorphism with references struct B { virtual void f() { cout << "B" <<

Polymorphism with references struct B { virtual void f() { cout << "B" << endl; } }; struct D : public B { void f() { cout << "D" << endl; } }; 54 int main() { D d; B b=d; b. f(); } int main() { D d; B& b=d; b. f(); }