Presentation is loading. Please wait.

Presentation is loading. Please wait.

C++ Certificate Program C++ Intermediate

Similar presentations


Presentation on theme: "C++ Certificate Program C++ Intermediate"— Presentation transcript:

1 C++ Certificate Program C++ Intermediate
Object Creation, Copying, Lifetime Management ©Fraser Hutchinson & Cliff Green

2 Constructors and Destructors (Review!)
Essential part of Object-Oriented Languages Enforces initialization and cleanup. These represent a large segment of errors in C Allow object invariance to be implemented -- state of an object can be known once constructed (no ‘intermediate’ state) Use of any non-trivial type assumes some initialization be performed prior to first use In C, this is traditionally via an init() or initialize() routine In C++ this initialization is paramount, never left to class user / client to determine if and when ©Fraser Hutchinson & Cliff Green

3 Ctor and Dtor Details Constructors and destructors are similar to other methods except that they: Automatically call base ctors and dtors Automatically call member data ctors and dtors Have no return type Destructors take no arguments ©Fraser Hutchinson & Cliff Green

4 Memory Allocation Constructor does not allocate memory for an object
For automatic objects the compiler allocates memory For free store objects, memory is allocated by ‘new’ operator implementation ©Fraser Hutchinson & Cliff Green

5 Memory Deallocation Destructors do not deallocate memory
Memory deallocation is responsibility of ‘delete’ operator or the compiler ©Fraser Hutchinson & Cliff Green

6 Automatic Objects Constructors and destructors are automatically called when objects are created When execution leaves object scope, object is destroyed / destructor is automatically called ©Fraser Hutchinson & Cliff Green

7 Example class X {…}; void f() {
X x; // automatically calls constructor //object is destroyed here when going out of // scope and destructor invoked } ©Fraser Hutchinson & Cliff Green

8 Free-Store Objects Dynamically created objects allocated via call to operator new Constructor called via operator new Objects created dynamically must be explicitly destroyed via operator delete ©Fraser Hutchinson & Cliff Green

9 Example class X {…}; void f() {
X *x = new X; // constructor invoked after // new() delete x; // destructor invoked via // delete } ©Fraser Hutchinson & Cliff Green

10 Compiler Generated Default Ctor
Class can have many overloaded constructors but only one destructor Obvious as destructors take no parameters If no constructors declared for a class, compiler will generate a default ctor (no parameters), performs default initialization of attributes Recursively invokes base default ctor, member data default ctors (or nothing, if built-in type) ©Fraser Hutchinson & Cliff Green

11 Implicit Constructor Invocation
C++ provides a simplified form of calling a constructor with a single parameter (masked via ‘=‘ symbol) At first glance invokes assignment operator, but instead invokes matching constructor ©Fraser Hutchinson & Cliff Green

12 Example class X { public: X (X const &); // copy ctor X (int);
X (std::string const &); X (double); private: // disable copy assignment from client use X& operator= (X const &); }; int f() { X x1 = 5; // X(int) X x = x1; // X(X const &) X x3 = 5.0; // X(double); } ©Fraser Hutchinson & Cliff Green

13 Explicit Constructor Motivation
Sometimes semantically confusing class MyVector { Public: MyVector (int size = 0); }; int foo() { MyVector states = 50; // just what does // this mean? } ©Fraser Hutchinson & Cliff Green

14 Example Sometimes incorrect logic
void v (MyVector<int> const& vec) { if (vec(10) == 20) { // ack! … will compile // … } // correct logic was meant to be // if (vec[10] == 20) What does the incorrect line actually do? ©Fraser Hutchinson & Cliff Green

15 Explicit Semantics Explicit keyword disallows implicit conversions for single argument constructors In some cases failing to use explicit keyword can results in unintended errors ©Fraser Hutchinson & Cliff Green

16 Explicit Keyword Added
class MyVector { Public: explicit MyVector (int size = 0); }; void v (MyVector<int> const& vec) { if (vec(10) == 20) { // now syntax error // … if (vec(10) == vec(20)) { // compiles due to explicit construction, // although questionable logic } ©Fraser Hutchinson & Cliff Green

17 Explicit Constructors
Explicit constructors still allow construction with appropriate type, but disallow implicit conversion as part of the construction General rule is to make single parameter constructors explicit unless implicit conversion desired functionality ©Fraser Hutchinson & Cliff Green

18 Avoiding Redundancy in Constructors
Often need to declare default constructors Appropriate design for class Certain uses of STL require it (primarily in container ctors that construct with a default value, or in resizing to a larger size) Sometimes difference between default and non-default constructor is only in values assigned to members Almost always true for constructors that just perform raw initialization Instead of two constructors, use default values for all arguments Still equivalent to a default constructor ©Fraser Hutchinson & Cliff Green

19 Example, Redundant Ctors
class X { public: X() :ival(0), dval(0.0) {} X(int a = 0, double d = 0.0) : ival(a), dval(d) {} private: int ival; double dval; }; int f() { X x1; // X() : ival = 0, dval = 0.0 X x2(5); // illegal call! Both or neither X x3(5,10.5); // X(int,double) : ival = 5, dval = 10.5 } ©Fraser Hutchinson & Cliff Green

20 Example, No Redundant Ctors
class X { public: X(int a = 0, double d = 0.0) : ival(a), dval(d) {} private: int ival; double dval; }; int f() { X x1; // ival = 0, dval = 0.0 X x2(5); // ival = 5, dval = 0.0 X x3(5,10.5); // ival = 5, dval = 10.5 } ©Fraser Hutchinson & Cliff Green

21 Initializer Lists Code within ctor body must be able to make certain assumptions Should have known, sane state on entering ctor body: should be able to assume that base data and all members (attributes) are initialized for ctor body If these assumptions couldn’t be made, semantics of ctor bodies would be complex and error-prone ©Fraser Hutchinson & Cliff Green

22 Guaranteed Initialization
C++ ensures all initialization is performed prior to entering the constructor body Problem: How to have control over construction / initialization of base and member data? ©Fraser Hutchinson & Cliff Green

23 Initializer Lists C++ provides initializer lists
These effectively provide a "pre-body" with special syntax Initializer lists only used for initialization of base and member data, in constructors Exception safety in initialization blocks is achieved via function try blocks ©Fraser Hutchinson & Cliff Green

24 Why Use Initialization Lists?
Two primary reasons: In certain cases, no other syntax will allow initialization Efficiency ©Fraser Hutchinson & Cliff Green

25 Required Uses Base object construction: If base type does not provide default constructor, initializer list must be used to provide initialization values Member data construction: Similar to base object construction (if default ctor not provided in member’s class, must use initializer list) Initialization of constant members: Must be initialized through initializer list Initialization of references: Member references must be initialized through initializer list ©Fraser Hutchinson & Cliff Green

26 Efficiency Can be inefficient (or awkward) to default construct member data, then assign to it (in ctor body) Just as in any other code Meaningful constructors should be used over default construction followed by assignment ©Fraser Hutchinson & Cliff Green

27 More on Initializer Lists
Default construction in the initializer list provided through empty parentheses (built-in types have language specified default values, typically some form of 0) Arrays cannot be initialized through init lists, populating array must be performed in constructor body (or elsewhere) ©Fraser Hutchinson & Cliff Green

28 Example class Engine { public:
explicit Engine (std::string const& description = "", int const horsepower = 200) : mDescription(description), mHorsepower(horsepower) {} private: std::string mDescription; int mHorsepower; }; class Suspension { explicit Suspension (bool sportTuned = false) : mSportTuned(sportTuned) {} bool mSportTuned; ©Fraser Hutchinson & Cliff Green

29 Example class Vehicle { public: Vehicle (int wheels)
: mSusp(), mEngine(), mNumWheels(wheels) {} private: Suspension mSusp; Engine mEngine; int const mNumWheels_; }; class Sedan : public Vehicle Sedan () : Vehicle (4) {} // … ©Fraser Hutchinson & Cliff Green

30 Recommendation In every constructor, construct/initialize all base objects and every member in the initializer list; use empty parentheses syntax for default construction Specify members in initializer list in same order as are declared in the class (class members are initialized in the order they are declared) ©Fraser Hutchinson & Cliff Green

31 Deferred Construction and Pre-destruction
Constructors and destructors are an integral part of OO languages However, some dismiss them as ‘syntactic sugar’ They instead employ a C idiom, which in the OO world is referred to as deferred construction: No meaningful ctors, instead performing “initialization” at a later time Classic C init() or initialize() methods … ©Fraser Hutchinson & Cliff Green

32 Example class Y { public: Y() : mInt(0), mDouble(0.0), mZptr(0) {}
void init (int i, double d) { mInt = i; mDouble = d; mZptr = new Z; } void deinit() { delete mZptr; mZptr = 0; private: int mInt; double const mDouble; Z* mZptr; }; ©Fraser Hutchinson & Cliff Green

33 Design Consequences Without constructors cannot use constants or references in the class (forced to use non-const pointers instead of references) Invariance is thrown out the window, resulting in poor design Pre-destruction (through deinit()) complicates object usage, potentially leaving it in an unstable state ©Fraser Hutchinson & Cliff Green

34 More Advice Constructors, destructors, and initializer lists are not syntactic sugar – are essential features of the C++ language Constructors are present in almost all OO languages - well defined and well tested in their utility Not using them will result in overly complicated object states and potentially complex, inefficient, and error-prone code ©Fraser Hutchinson & Cliff Green

35 Object Integrity C++ (and most OO languages) allow a designer to guarantee the consistency and integrity of an object throughout its lifetime (outside of purposeful attempts to corrupt an object) Made possible through construction, destruction, and encapsulation semantics Deferred construction and "pre-destruction" prevent or complicate the designer's ability to guarantee the consistency and integrity of an object ©Fraser Hutchinson & Cliff Green

36 Constructing Arrays Array usage can be difficult for a number of reasons - STL containers contain useful functionality not present in arrays Recall that arrays of objects can be dynamically allocated by using the new[] operator Employee* employees = new Employee [100]; // … delete[] employees; // delete employees; would be error – // what could happen and why? ©Fraser Hutchinson & Cliff Green

37 Constructors, Destructors and Arrays
Review: default ctor called for each element of the array when new’ed In the example, 100 Employee ctors will be called when allocating the employees array Ctors will be called regardless of whether all objects will be used Delete[] will invoke 100 destructors Less than ideal for some purposes ©Fraser Hutchinson & Cliff Green

38 What About Expansion? C++ provides only one means of "growing" a dynamically allocated array: realloc() Realloc is part of the C standard library and not designed to play nicely with the object-oriented world of C++ - specifically ctors and dtors are not invoked ©Fraser Hutchinson & Cliff Green

39 Example, Undesirable Approach
Employee* moreEmployees = new Employee [200]; for (int i = 0; i < 100; ++i) { moreEmployees [i] = employees [i]; } delete[] employees; employees = moreEmployees; ©Fraser Hutchinson & Cliff Green

40 Behavior Will invoke 200 default constructors, 100 assignment operators and 100 destructors Expensive performance-wise, error-prone, hard to maintain Usually not acceptable to a developer - what can be done? ©Fraser Hutchinson & Cliff Green

41 Another Approach Better performance: allocate an array of pointers to objects Pointers can be initialized to 0 until the objects are allocated Allows construction only as needed Default constructors not required - can initialize objects as desired ©Fraser Hutchinson & Cliff Green

42 Example Employee** moreEmployees = new Employee* [200];
// copy pointers from employees array – could // be STL algorithm or using memcpy // set remaining contents of new array to 0, // could STL algorithm or memset delete[] employees; employees = moreEmployees; ©Fraser Hutchinson & Cliff Green

43 Comparison Runs far faster than original version
All extra work related to calling assignment operators and extra ctors and dtors has been eliminated However, code is more complex, and maintenance issues may start to arise A more modern approach, using the boost library for its handle classes, might be: ©Fraser Hutchinson & Cliff Green

44 Example Using Boost Ptr Class
#include <vector> #include <boost/smart_ptr.hpp> // smart pointer classes int main () { typedef boost::shared_ptr<Employee> HEmployee; std::vector<HEmployee> employees; employees.reserve(100); // space pre-allocation // Allocate each object individually (with appropriate // constructor), add to the end of the container employees.push_back(HEmployee(new Employee(…))); //… add other Employees as needed // increase capacity to 200, preserving originals employees.reserve(200); // All destruction and cleanup taken care of by // the container and handles return 0; } ©Fraser Hutchinson & Cliff Green

45 Consequences Using a standard library container and a handle simplifies the code (especially memory management logic) In last two examples, the array or container is storing pointers to Employee objects, rather than Employee objects themselves Can provide substantial savings in object memory usage and processing time (because object copying is avoided) Using pointers or handles to objects can simplify object association and object uniqueness issues ©Fraser Hutchinson & Cliff Green

46 Exception Safety in Constructors
Two kinds of behaviors should be associated with properly written code that uses exceptions Code should be exception-safe and exception-neutral For the following definitions, exception could be thrown directly by method, or indirectly through a function called by the method ©Fraser Hutchinson & Cliff Green

47 Exception-Safe Method is defined to be exception-safe if, upon throwing an exception: no resources are leaked the object or system state remains valid This guarantee sometimes referred to as the basic or weak exception-safety guarantee ©Fraser Hutchinson & Cliff Green

48 Exception-Neutral A method is defined to be exception-neutral (I.e. provides the strong exception safety guarantee) if the following condition holds true: If the method terminates by propagating an exception, any changes to the state of the application are rolled back or uncommitted ©Fraser Hutchinson & Cliff Green

49 Example From Exceptional C++
template <typename Element> class Stack { public: Stack(); ~Stack(); // … private: Element* vector_; size_t size_; size_t used_; }; Stack<Element>::Stack() : vector(0), size(10), used(0) { vector = new Element [size]; } ©Fraser Hutchinson & Cliff Green

50 Class Functionality Clearly Stack is a container, required to manage dynamic memory resources Important to ensure no leaks, even when exceptions thrown by Element type operations (e.g. in Element default constructor) or standard memory allocations ©Fraser Hutchinson & Cliff Green

51 Is This Ctor Exception-Safe and Exception-Neutral?
Nothing in the initializer list can throw Statement in ctor body first tries to call operator new[], either default version or one provided by Element, then tries to call Element default constructor ‘size’ times Two operations might throw exceptions: Memory allocation itself, thowing std::bad_alloc Element default constructor (could throw anything) – language guarantees any constructed objects are destroyed (invoking dtor), allocated memory is returned via operator delete[] ©Fraser Hutchinson & Cliff Green

52 Is Default Constructor Robust?
Yes, for the following reasons: No resource (memory in this case) leaks Stack will be in a consistent state whether or not any part of the initialization throws Exception-neutral – any thrown exception is correctly propagated up to caller “Proto-object” never became a completely constructed object ©Fraser Hutchinson & Cliff Green

53 Is Destructor Robust? template <typename Element> Stack<Element>::~Stack() { delete[] vector_; // can't throw } Yes – delete[]’ing the array invokes Element destructor for each object in the array, then calls operator delete to deallocate heap memory (which will never throw) Stack requires Element dtor to never throw ©Fraser Hutchinson & Cliff Green

54 Is Push Robust? Traditional implementations are not exception safe
template <typename Element> void Stack<Element>::push(Element const& val) { // implementation of push method } Traditional implementations are not exception safe Exercise – design an implementation that is exception-safe (hint – take advantage of language construction guarantees) ©Fraser Hutchinson & Cliff Green

55 The Resource Acquisition Is Initialization Technique (RAII)
Exploit constructor and destructor behavior via object scope Guarantee resource cleanup using language scoping rules (dtor of local / stack objects will always be invoked when the object goes out of scope) ©Fraser Hutchinson & Cliff Green

56 Example class InvocationDocumenter { public:
InvocationDocumenter (std::string const& func) : mFunc(func) { std::cout << "Entering " << mFunc << std::endl; } ~InvocationDocumenter () { std::cout << "Leaving " << mFunc << std::endl; private: std::string const mFunc; ; // example usage void Cactus::sitStill () { InvocationDocumenter invocation ("Cactus::sitStill"); // do lots of stuff: return anywhere, throw an exception, // do whatever you want, you can't stop this documenter // from printing when you leave the function, short of // crashing the application ©Fraser Hutchinson & Cliff Green

57 RAII Usage Use the technique whenever something should automatically happen at end of a given scope, for example: opening/closing a file no matter how a given method completes or throws releasing any resource, such as a Windows HANDLE or a BSD Socket documenting the lifetimes of methods and objects performing timing of a given section of code documenting lifetime of a dynamically loaded library (DLL/shared lib) via a global or anonymously namespaced RAII object ©Fraser Hutchinson & Cliff Green

58 Handles Presents an interface for another class whose representation should be hidden from user Used in place of the true, underlying object Operations are performed on handle class, which forwards them on to internal object ©Fraser Hutchinson & Cliff Green

59 Handle Classes Representation can vary independently since work done through handle interface Frequently interfaces forward via the pointer-to (operator->) and dereference (unary operator*) operators A Decorator could be considered a handle class - provides same interface as component it decorates, forwards requests through to actual component Not just an OO concept – MS Windows uses handles to decouple application from actual representation of various OS-defined "objects,” such as windows, hardware devices, and fonts ©Fraser Hutchinson & Cliff Green

60 Simple Example class HCar { public: HCar (Car* representation)
: mCarRep(representation) {} Car* operator-> () { return mCarRep; } private: Car* MCarRep; }; HCar car (new Civic()); car->accelerate(); ©Fraser Hutchinson & Cliff Green

61 auto_ptr<T> Std library facility for safer handling of pointers
Uses RAII to accomplish its main goal: automatic pointer deletion at the end of its scope ©Fraser Hutchinson & Cliff Green

62 Example #include <memory>
std::auto_ptr<Car> civic (new Civic()); // the following line changes the owner // of the pointer to primary car, which // invalidates civic std::auto_ptr<Car> primaryCar (civic); primaryCar->accelerate(); // do not dereference civic at this point ©Fraser Hutchinson & Cliff Green

63 auto_ptr<T> Semantics
1998 C++ standard defines as follows: An auto_ptr owns the object it holds a pointer to. Copying an auto_ptr copies the pointer and transfers ownership to the destination. If more than one auto_ptr owns the same object at the same time the behaviour of the program is undefined. Strict ownership policy is used for simplicity and storage efficiency Since copy construction and assignment do not follow the usual logic (source and destination are “equivalent”), auto_ptr cannot be used in containers or arrays ©Fraser Hutchinson & Cliff Green

64 Candidates Short-lived, dynamically allocated objects
“Source / Sink” functions Objects created dynamically and need to be destroyed only in the case of an exception Objects with complex lifetimes, or with sharing needs (e.g. passed throughout an application) require more robust handles, such as boost::shared_ptr ©Fraser Hutchinson & Cliff Green

65 Constraints auto_ptr cannot handle arrays allocated with new[] – always performs single object delete, cannot deduce array versus single object Important to note with almost all pointer handle implementations ©Fraser Hutchinson & Cliff Green


Download ppt "C++ Certificate Program C++ Intermediate"

Similar presentations


Ads by Google