Presentation is loading. Please wait.

Presentation is loading. Please wait.

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

Similar presentations


Presentation on theme: "©Fraser Hutchinson & Cliff Green C++ Certificate Program C++ Intermediate Object Creation, Copying, Lifetime Management."— Presentation transcript:

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

2 ©Fraser Hutchinson & Cliff Green 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

3 ©Fraser Hutchinson & Cliff Green 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

4 ©Fraser Hutchinson & Cliff Green 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

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

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

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

8 ©Fraser Hutchinson & Cliff Green 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

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

10 ©Fraser Hutchinson & Cliff Green 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)

11 ©Fraser Hutchinson & Cliff Green 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

12 ©Fraser Hutchinson & Cliff Green 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); }

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

14 ©Fraser Hutchinson & Cliff Green Example Sometimes incorrect logic void v (MyVector 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?

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

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

17 ©Fraser Hutchinson & Cliff Green 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

18 ©Fraser Hutchinson & Cliff Green 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

19 ©Fraser Hutchinson & Cliff Green 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 }

20 ©Fraser Hutchinson & Cliff Green 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 }

21 ©Fraser Hutchinson & Cliff Green 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

22 ©Fraser Hutchinson & Cliff Green 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?

23 ©Fraser Hutchinson & Cliff Green 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

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

25 ©Fraser Hutchinson & Cliff Green 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

26 ©Fraser Hutchinson & Cliff Green 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

27 ©Fraser Hutchinson & Cliff Green 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)

28 ©Fraser Hutchinson & Cliff Green 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 { public: explicit Suspension (bool sportTuned = false) : mSportTuned(sportTuned) {} private: bool mSportTuned; };

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

30 ©Fraser Hutchinson & Cliff Green 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)

31 ©Fraser Hutchinson & Cliff Green 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 …

32 ©Fraser Hutchinson & Cliff Green 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; };

33 ©Fraser Hutchinson & Cliff Green 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

34 ©Fraser Hutchinson & Cliff Green 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

35 ©Fraser Hutchinson & Cliff Green 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

36 ©Fraser Hutchinson & Cliff Green 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?

37 ©Fraser Hutchinson & Cliff Green 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

38 ©Fraser Hutchinson & Cliff Green 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

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

40 ©Fraser Hutchinson & Cliff Green 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?

41 ©Fraser Hutchinson & Cliff Green 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

42 ©Fraser Hutchinson & Cliff Green 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;

43 ©Fraser Hutchinson & Cliff Green 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:

44 ©Fraser Hutchinson & Cliff Green Example Using Boost Ptr Class #include #include // smart pointer classes int main () { typedef boost::shared_ptr HEmployee; std::vector 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; }

45 ©Fraser Hutchinson & Cliff Green 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

46 ©Fraser Hutchinson & Cliff Green 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

47 ©Fraser Hutchinson & Cliff Green 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

48 ©Fraser Hutchinson & Cliff Green 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

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

50 ©Fraser Hutchinson & Cliff Green 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

51 ©Fraser Hutchinson & Cliff Green 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[]

52 ©Fraser Hutchinson & Cliff Green 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

53 ©Fraser Hutchinson & Cliff Green Is Destructor Robust? template Stack ::~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

54 ©Fraser Hutchinson & Cliff Green Is Push Robust? template void Stack ::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)

55 ©Fraser Hutchinson & Cliff Green 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)

56 ©Fraser Hutchinson & Cliff Green 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 }

57 ©Fraser Hutchinson & Cliff Green 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

58 ©Fraser Hutchinson & Cliff Green 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

59 ©Fraser Hutchinson & Cliff Green 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

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

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

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

63 ©Fraser Hutchinson & Cliff Green auto_ptr 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

64 ©Fraser Hutchinson & Cliff Green 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

65 ©Fraser Hutchinson & Cliff Green 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


Download ppt "©Fraser Hutchinson & Cliff Green C++ Certificate Program C++ Intermediate Object Creation, Copying, Lifetime Management."

Similar presentations


Ads by Google