590 likes | 669 Views
CIS 4930 Application Development Using C++ Dr. Kun Suk Kim CISE Department, University of Florida. Utilities. Caveat. Don’t use auto_ptr in a parameter list or as a return value if you don’t mean to transfer ownership template <class T> void bad_print(std::auto_ptr<T> p) {
E N D
CIS 4930Application Development Using C++Dr. Kun Suk KimCISE Department, University of Florida Utilities
Caveat • Don’t use auto_ptr in a parameter list or as a return value if you don’t mean to transfer ownership template <class T> void bad_print(std::auto_ptr<T> p) { if (p.get() == NULL) std::cout << “NULL”; else std::cout << *p; } // Oops, exiting deletes the object to which p refers std::auto_ptr<int> p(new int); *p = 42; bad_print(p); // Oops, deletes the memory to which p refers *p = 18; // RUNTIME ERROR
Caveat • Transfer of ownership is not possible with constant references const std::auto_ptr<int> p(new int); *p = 42; bad_print(p); // COMPILE-TIME ERROR *p = 18; // OK
Caveat • Container classes use constant reference to get values that they copy internally template <class T> void container::insert(const T& value) { … x = value; // assign or copy value internally … } container<std::auto_ptr<int> > c; const std::auto_ptr<int> p(new int); … c.insert(p); // ERROR …
Caveat • Can’t change the ownership of a constant auto_ptr • Can change the value of the object to which it refers std::auto_ptr<int> f() { const std::auto_ptr<int> p(new int); // no ownership transfer possible std::auto_ptr<int> q(new int); // ownership transfer possible *p = 42; // OK, change value to which p refers bad_print(p); // COMPILE-TIME ERROR *p = *q; // OK, change value to which p refers p = q; // COMPILE-TIME ERROR return p; // COMPILE-TIME ERROR }
Auto_ptrs as Members • Avoid resource leaks within a class • No longer need a destructor • Helps to avoid resource leaks that are caused by exceptions that are thrown during the initialization of an object • Note: destructors are called only if any construction is completed • If an exception occurs inside a constructor, destructors are only called for objects that have been fully constructed
Auto_ptrs as Members ~ClassB() { delete ptr1; delete ptr2; } }; class ClassB { private: classA* ptr1; // pointer members classA* ptr2; public: // might cause resource leak if second new throws ClassB (ClassA val1, ClassA val2) : ptr1(new ClassA(val1)), ptr2(new ClassA(val2)) { } // might cause resource leak if second new throws ClassB (const ClassB& x) : ptr1(new ClassA(*x.ptr1)), ptr2(new ClassA(*x.ptr2)) { } const ClassB& operator= (const ClassB& x) { *ptr1 = *x.ptr1; *ptr2 = *x.ptr2; return *this; }
Auto_ptrs as Members class ClassB { private: const std::auto_ptr<classA> ptr1; // auto_ptr members const std::auto_ptr<classA> ptr2; public: // no resource leak possible ClassB (ClassA val1, ClassA val2) : ptr1(new ClassA(val1)), ptr2(new ClassA(val2)) { } // no resource leak possible ClassB (const ClassB& x) : ptr1(new ClassA(*x.ptr1)), ptr2(new ClassA(*x.ptr2)) { } const ClassB& operator= (const ClassB& x) { *ptr1 = *x.ptr1; *ptr2 = *x.ptr2; return *this; } // no destructor necessary };
Examples template <class T> ostream& operator<< (ostream& strm, const auto_ptr<T>& p){ if (p.get() == NULL) strm << "NULL"; else strm << *p; return strm; } auto_ptr<int> p(new int(42)); auto_ptr<int> q; cout << "p: " << p << " q: " << q << endl; q = p; cout << "p: " << p << " q: " << q << endl; *q += 13; p = q; cout << "p: " << p << " q: " << q << endl; p: 42 q: NULL p: NULL q: 42 p: 55 q: NULL
Examples template <class T> ostream& operator<< (ostream& strm, const auto_ptr<T>& p){ if (p.get() == NULL) strm << "NULL"; else strm << *p; return strm; } const auto_ptr<int> p(new int(42)); const auto_ptr<int> q(new int(0)); const auto_ptr<int> r; cout << "p: " << p << " q: " << q << " r: " << r << endl; *q = *p; // *r = *p; // ERROR: undefined behavior *p = -77; cout << "p: " << p << " q: " << q << " r: " << r << endl; // q = p; r = p; // ERROR at compile time p: 42 q: 0 r: NULL p: -77 q: 42 r: NULL
Misusing auto_ptrs • Auto_ptrs cannot share ownership • Auto_ptrs are not provided for arrays • Auto_ptr calls delete instead of delete[] • Auto_ptrs are not “universal smart pointers” • Are not pointers for reference counting • Auto_ptrs don’t meet the requirements for container elements
Requirements for Container Elements • Copyable by a copy constructor • Assignable by the assignment operator • Destroyable by a destructor • For some member functions of sequence containers, the default constructor must be available • For several operations, the test of equality with operator == must be defined • For associative containers, the operations of the sorting criterion must be provided by the elements
Value Semantics or Reference Semantics • STL containers provide value semantics • Strengths: • Copying elements is simple • References are error prone • Dangling pointers, circular references • Weaknesses: • Copying elements might result in bad performance or may not even be possible • Managing the same object in several containers at the same time is not possible • To get reference semantics for STL containers you must write your own smart pointer class
Implementing Reference Semantics template <class T> class CountedPtr { private: T* ptr; long* count; public: explicit CountedPtr (T* p=0) : ptr(p), count(new long(1)) { } CountedPtr (const CountedPtr<T>& p) throw() : ptr(p.ptr), count(p.count) { ++*count; } ~CountedPtr () throw() { dispose(); }
Implementing Reference Semantics CountedPtr<T>& operator= (const CountedPtr<T>& p) throw() { if (this != &p) { dispose(); ptr = p.ptr; count = p.count; ++*count; } return *this; } T& operator*() const throw() { return *ptr; } T* operator->() const throw() { return ptr; } private: void dispose() { if (--*count == 0) { delete count; delete ptr; } } };
Implementing Reference Semantics void printCountedPtr (CountedPtr<int> elem){ cout << *elem << ' '; } int main(){ static int values[] = { 3, 5, 9, 1, 6, 4 }; typedef CountedPtr<int> IntPtr; deque<IntPtr> coll1; list<IntPtr> coll2; for (int i=0; i<sizeof(values)/sizeof(values[0]); ++i) { IntPtr ptr(new int(values[i])); coll1.push_back(ptr); coll2.push_front(ptr); } for_each (coll1.begin(), coll1.end(), printCountedPtr); cout << endl;
Implementing Reference Semantics for_each (coll2.begin(), coll2.end(), printCountedPtr); cout << endl << endl; *coll1[2] *= *coll1[2]; (**coll1.begin()) *= -1; (**coll2.begin()) = 0; for_each (coll1.begin(), coll1.end(), printCountedPtr); cout << endl; for_each (coll2.begin(), coll2.end(), printCountedPtr); cout << endl; } 3 5 9 1 6 4 4 6 1 9 5 3 -3 5 81 1 6 0 0 6 1 81 5 -3
Auxiliary Functions • Three auxiliary functions • The selection of minimum and maximum of two values • The swapping of two values • Defined in header file <algorithm>
Processing the Minimum and Maximum • If both values are equal, generally the first element gets returned • It is not good programming style to rely on this namespace std { template <class T> inline const T& min (const T& a, const T& b) { return b < a ? b : a; } template <class T> inline const T& max (const T& a, const T& b) { return a < b ? b : a; } }
Processing the Minimum and Maximum • Functions with the comparison criterion as an additional argument • A function or a function object namespace std { template <class T, class Compare> inline const T& min (const T& a, const T& b, Compare comp) { return comp(b,a) ? b : a; } template <class T , class Compare> inline const T& max (const T& a, const T& b, Compare comp) { return comp(a,b) ? b : a; } }
Processing the Minimum and Maximum • The definition of min() and max() require that both types match int i; long l; … l = std::max(i,l); // ERROR: argument types don’t match • Should qualify explicitly the type of arguments l = std::max<long>(i,l); // OK
Swapping Two Values • The call std::swap(x,y) is possible only if • The copy constructions and assignments inside the swap function are possible namespace std { template <class T> inline void swap (T& a, T& b) { T tmp(a); // copy construction a = b; // assignment b = tmp; // assignment } }
Swapping Two Values • Swap() enables to provide special implementations for more complex types • By using template specialization or function overloading • The containers have the same type and the source is no longer used • Swaps only internal data of the containers • Constant complexity • Assignment of containers • copy all elements of the source container and remove all old elements in the destination container • Linear complexity
Swapping Two Values class MyContainer { private: int* elems; int numElems; public: void swap (MyContainer& x) { std::swap(elems, x.elems); std::swap(numElems, x.numElems); } … }; • overloaded global swap() for this type Inline void swap(MyContainer& c1, MyContainer& c2) { c1.swap(c2); }
Supplementary Comparison Operators • Four template functions • Define comparison operators !=, >, <=, and >= • Defined in <utiltiy> namespace std { namespace rel_ops { template <class T> inline bool operator!= (const T& x, const T& y) { return !(x == y); } template <class T> inline bool operator> (const T& x, const T& y) { return y < x; } template <class T> inline bool operator<= (const T& x, const T& y) { return !(y < x); } template <class T> inline bool operator>= (const T& x, const T& y) { return !(x < y); } } }
Supplementary Comparison Operators • To use them, only define operators < and == #include <utility> class X { public: bool operator== (const X& x) const; bool operator< (const X& x) const; … } void foo() { using namespace std::rel_ops; // make !=, >, >=, and <= available X x1, x2; … if (x1 != x2) { … } if (x1 > x2) { … } }
Numeric Limits • Numeric type have platform-dependent limits • Template numeric_limits • Replace and supplement the ordinary preprocessor constants of C • Offers more type safer • Enables a programmer to write templates that evaluate these limits • C constants are available • Integer types in <climits> • Floating-point types in <cfloat>
Class numeric_limits<> • A general template provides the default numeric values for any type • No specialization for numeric limits as default for any type namespace std { template <class T> class numeric_limits { public: static const bool is_specialized = false; … }; }
Class numeric_limits<> • Specializations of the template define the numeric limits for each numeric type namespace std { template<> class numeric_limits<int> { public: static const bool is_specialized = true; static int min() throw() { return –2147483648; } static int max() throw() { return 2147483647; } static const int digits = 31; … }; }
Class numeric_limits<> • The general numeric_limits template and its specializations are provided in the head <limits> • The specializations are provided for any fundamental type • bool, char, signed char, unsigned char, short, unsigned short, int, unsigned int, long, unsinged long, float, double, and long double
Specialization of Numeric Limits for Type float namespace std { template<> class numeric_limits<float> { public: static const bool is_specialized = true; inline static float min() throw() { return 1.17549435E-38F; } inline static float max() throw() { return 3.40282347E+38F; } static const int digits = 24; static const int digits10 = 6; static const bool is_signed = true; static const bool is_integer = false; static const bool is_exact = false; static const bool is_bounded = true; static const bool is_modulo = false; static const bool is_iec559 = true;
Specialization of Numeric Limits for Type float static const int radix = 2; inline static float epsilon() throw() { return 1.19209290E-07F; } static const float_round_style round_style = round_to_nearest; inline static float round_error() throw() { return 0.5F; } static const int min_exponent = -125; static const int max_exponent = +128; static const int min_exponent10 = -37; static const int max_exponent10 = +38; static const bool has_infinity = true; inline static float infinity() throw() { return …; } static const bool has_quiet_NaN = true; inline static float quiet_NaN() throw() { return …; } static const bool has_signaling_NaN = true; inline static float signaling_NaN() throw() { return …; }
Specialization of Numeric Limits for Type float static const float_denorm_style has_denorm = denorm_absent; static const bool has_denorm_loss = false; inline static float denorm_min() throw() { return min(); } static const bool traps = true; static const bool tinyness_before = true; }; }
Example of Using numeric_limits<> cout << "short: " << numeric_limits<short>::max() << endl; cout << "int: " << numeric_limits<int>::max() << endl; cout << "long: " << numeric_limits<long>::max() << endl; cout << "float: " << numeric_limits<float>::max() << endl; cout << "double: " << numeric_limits<double>::max() << endl; cout << "long double: " << numeric_limits<long double>::max() << endl; cout << "is_signed(char): " << numeric_limits<char>::is_signed << endl; cout << "is_specialized(string): " << numeric_limits<string>::is_specialized << endl; short: 32767 int: 2147483647 long: 2147483647 float: 3.40282e+038 double: 1.79769e+308 long double: 1.79769e+308 is_signed(char): 1 is_specialized(string): 0
CIS 4930Application Development Using C++Dr. Kun Suk KimCISE Department, University of Florida Virtual Functions
Objectives • Virtual Function Basics • Late binding • Implementing virtual functions • When to use a virtual function • Abstract classes and pure virtual functions • Pointers and Virtual Functions • Extended type compatibility • Downcasting and upcasting • C++ ‘under the hood’ with virtual functions
Virtual Function Basics • Polymorphism • Associating many meanings to one function • Virtual functions provide this capability • Fundamental principle of object-orientedprogramming! • Virtual • Existing in ‘essence’ though not in fact • Virtual Function • Can be ‘used’ before it’s ‘defined’
Figures Example • Best explained by example: • Classes for several kinds of figures • Rectangles, circles, ovals, etc. • Each figure an object of different class • Rectangle data: height, width, center point • Circle data: center point, radius • All derive from one parent-class: Figure • Require function: draw() • Different instructions for each figure
Figures Example 2 • Each class needs different draw function • Can be called ‘draw’ in each class, so:Rectangle r;Circle c;r.draw(); //Calls Rectangle class’s drawc.draw(); //Calls Circle class’s draw • Nothing new here yet…
Figures Example: center() • Parent class Figure contains functionsthat apply to ‘all’ figures; consider:center(): moves a figure to center of screen • Erases first, then re-draws • So Figure::center() would use function draw()to re-draw • Complications! • Which draw() function? • From which class?
Figures Example: New Figure • Consider new kind of figure comes along:Triangle class derived from Figure class • Function center() inherited from Figure • Will it work for triangles? • It uses draw(), which is different for each figure! • It will use Figure::draw() won’t work for triangles • Want inherited function center() to use functionTriangle::draw() NOT function Figure::draw() • But class Triangle wasn’t even WRITTEN whenFigure::center() was! Doesn’t know ‘triangles’!
Figures Example: Virtual! • Virtual functions are the answer • Tells compiler: • “Don’t know how function is implemented” • “Wait until used in program” • “Then get implementation from objectinstance” • Called late binding or dynamic binding • Virtual functions implement late binding
Virtual Functions: Another Example • Bigger example best to demonstrate • Record-keeping program for automotiveparts store • Track sales • Don’t know all sales yet • First only regular retail sales • Later: Discount sales, mail-order, etc. • Depend on other factors besides just price, tax
Virtual Functions: Auto Parts • Program must: • Compute daily gross sales • Calculate largest/smallest sales of day • Perhaps average sale for day • All come from individual bills • But many functions for computing bills willbe added ‘later’! • When different types of sales added! • So function for ‘computing a bill’ will bevirtual!
Class Sale Definition • class Sale{public: Sale(); Sale(double thePrice); double getPrice() const;virtual double bill() const; double savings(const Sale& other) const;private: double price;};
Member Functions savings and operator < • double Sale::savings(const Sale& other) const{ return (bill() – other.bill());} • bool operator < ( const Sale& first, const Sale& second){ return (first.bill() < second.bill());} • Notice BOTH use member function bill()!
Class Sale • Represents sales of single item with noadded discounts or charges. • Notice reserved word ‘virtual’ indeclaration of member function bill • Impact: Later, derived classes of Sale candefine THEIR versions of function bill • Other member functions of Sale will useversion based on object of derived class! • They won’t automatically use Sale’s version!