©Fraser Hutchinson & Cliff Green C++ Certificate Program C++ Intermediate Object Creation, Copying, Lifetime Management
©Fraser Hutchinson & Cliff Green
C++ Certificate ProgramC++ Intermediate
Object Creation, Copying, Lifetime Management
©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
©Fraser Hutchinson & Cliff Green
Ctor and Dtor DetailsConstructors 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
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
Memory Deallocation
• Destructors do not deallocate memory
• Memory deallocation is responsibility of ‘delete’ operator or the compiler
©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
©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
}
©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
©Fraser Hutchinson & Cliff Green
Example
class X {…};
void f() {X *x = new X; // constructor invoked after
// new()delete x; // destructor invoked via
// delete}
©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)
©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
©Fraser Hutchinson & Cliff Green
Exampleclass X {public:
X (X const &); // copy ctorX (int);X (std::string const &);X (double);
private:// disable copy assignment from client useX& 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
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
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
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
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
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
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
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.0X x2(5); // illegal call! Both or neitherX x3(5,10.5); // X(int,double) : ival = 5, dval = 10.5
}
©Fraser Hutchinson & Cliff Green
Example, No Redundant Ctorsclass 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.0X x2(5); // ival = 5, dval = 0.0X x3(5,10.5); // ival = 5, dval = 10.5
}
©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
©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?
©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
©Fraser Hutchinson & Cliff Green
Why Use Initialization Lists?
• Two primary reasons:– In certain cases, no other syntax will allow
initialization– Efficiency
©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
©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
©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)
©Fraser Hutchinson & Cliff Green
Exampleclass 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;
};
©Fraser Hutchinson & Cliff Green
Exampleclass 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) {}// …
};
©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)
©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 …
©Fraser Hutchinson & Cliff Green
Exampleclass 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
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
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
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
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
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
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
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
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
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
ExampleEmployee** 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
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
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
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
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
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
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
Example From Exceptional C++
template <typename Element> class Stack {public:
Stack();~Stack();// …
private:Element* vector_;size_t size_;size_t used_;
};
template <typename Element>Stack<Element>::Stack() : vector(0), size(10), used(0) {
vector = new Element [size];}
©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
©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[]
©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
©Fraser Hutchinson & Cliff Green
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
Is Push Robust?
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
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
Exampleclass 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 usagevoid 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
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
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
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
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
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
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
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
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
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