Constructors, Destructors & Memory Management Computer Science I Constructors: A Review! A member function with the same name as its class class X { public: X( ); for class X }; // constructor Used to create, and can initialize, objects of their class type Constructors: A Review! You cannot declare a constructor as virtual or static, nor can you declare a constructor as const, volatile, or const volatile You do not specify a return type for a constructor A return statement in the body of a constructor cannot have a return value Destructors Used to deallocate memory and do other cleanup for a class object and its class members when the object is destroyed Called for a class object when that object passes out of scope or is explicitly deleted Destructors A member function with the same name as its class prefixed by a ~ (tilde) For example: class X{ public: X(); class X ~X(); class X }; // Constructor for // Destructor for Destructors Takes no arguments and has no return type Its address cannot be taken Cannot be declared const, volatile, const volatile or static Can be declared virtual or pure virtual Implicitly Declared Destructors If no user-defined destructor exists for a class and one is needed, the compiler implicitly declares a destructor This implicitly declared destructor is an inline public member of its class Implicitly Declared Destructors The compiler will implicitly define an implicitly declared destructor when the compiler uses the destructor to destroy an object of the destructor's class type Implicitly Declared Destructors Suppose a class A has an implicitly declared destructor The following is equivalent to the function the compiler would implicitly define for A: A::~A( ) { } The compiler first implicitly defines the implicitly declared destructors of the base classes and non-static data members of a class A before defining the implicitly declared destructor of A Trivial vs. Nontrivial A destructor of a class A is trivial if all the following are true: It is implicitly defined All the direct base classes of A have trivial destructors The classes of all the non-static data members of A have trivial destructors If any of the above are false, then the destructor is nontrivial. Derived & Member Classes Class members that are class types can have their own destructors Both base and derived classes can have destructors, although destructors are not inherited If a base class A or a member of A has a destructor, and a class derived from A does not declare a destructor, a default destructor is generated Order of Execution The default destructor calls the destructors of the base class and members of the derived class The destructors of base classes and members are called in the reverse order of the completion of their constructor 1. The destructor for a class object is called before destructors for members and bases are called 2. Destructors for non-static members are called before destructors for base classes are called. 3. Destructors for non-virtual base classes are called before destructors for virtual base classes are called Exceptions When an exception is thrown for a class object with a destructor, the destructor for the temporary object thrown is not called until control passes out of the catch block Destructors Implicitly called when an automatic object (a local object that has been declared auto or register, or not declared as static or extern) or temporary object passes out of scope Implicitly called at program termination for constructed external and static objects Invoked when you use the delete operator for objects created with the new operator Example #include <string> class Y { private: char * string; int number; public: // Constructor Y(const char*, int); // Destructor ~Y() { delete[] string; } }; // Define class Y constructor Y::Y(const char* n, int a) { string = strcpy(new char[strlen(n) + 1 ], n); number = a; } int main () { // Create and initialize // object of class Y Y yobj = Y("somestring", 10); // ... // Destructor ~Y is called before // control returns from main() } Destructors You can use a destructor explicitly to destroy objects, although this practice is not recommended However to destroy an object created with the placement new operator, you can explicitly call the object's destructor Example #include <new> #include <iostream> using namespace std; class A { public: A( ) { cout << "A::A( )" << endl; } ~A( ) { cout << "A::~A( )" << endl; } }; int main ( ) { char* p = new char[sizeof(A)]; A* ap = new (p) A; ap->A::~A( ); delete [ ] p; } Destructors The statement A* ap = new (p) A dynamically creates a new object of type A not in the free store but in the memory allocated by p The statement delete [ ] p will delete the storage allocated by p But the run time will still believe that the object pointed to by ap still exists until you explicitly call the destructor of A (with the statement ap->A::~A( ) ) Garbage Collection and RAII Garbage Collection (GC) deals with the management of dynamic memory, with different levels of automation The construct, collector, attempts to reclaim garbage Garbage is memory that was used by application objects that will never be accessed or mutated again Garbage Collection and RAII This is often regarded as an important feature of recent languages, especially if they forbid manual memory management, that is very prone to errors and therefore requires an high level of experience from programmers Errors due to memory management result mostly in instabilities and crashes that are only noticed at runtime, making them extremely hard to detect and correct Garbage Collection & C++ C++ has optional support for garbage collection and some implementations include garbage collection do exist The C++ standard defines the implementation of the language and it's underlining platform opening it for the inclusion of extensions For instance, Sun's C++ compiler product does include the libgc library, a conservative garbage collector Garbage Collection & C++ Unlike many high level languages, C++ does not impose the use of garbage collection Mainstream C++ idioms for memory management do not assume the use of conventional automated garbage collection Resource Acquisition Is Initialization The most common garbage collection method in C++ is the use of the strangely named idiom "RAII (Resource Acquisition Is Initialization) The key idea is that a resource, whether acquired at initialization time or not, is owned by an object, and … That the object's destructor will automate the release of that resource at an appropriate time Resource Acquisition Is Initialization This enables C++, through RAII, to support deterministic cleanup of resources, since the same approaches that work for freeing memory can also be used to release other resources (i.e., file handles, mutexes, database connections, transactions, etc.) Resource Acquisition Is Initialization In the absence of a default garbage collection, RAII is a robust way to ensure that resources are not leaked even in code that might cause exceptions to be thrown It is arguably superior to the finally construct in Java and similar languages In C++ the class provides a destructor, and users of that class don't need to do anything except ensure that the object is destroyed when they are finished with it Smart Pointers for Memory Management Smart pointer type is any class type that overloads operator->, operator*, or operator>* They are not really pointers at all, but overloading these operators allows a smart pointer to behave much like a built-in pointer, and much code can be written which works with both "real" pointers and smart pointers std::auto_ptr The only smart pointer type included in the 2003 C++ Standard is std::auto_ptr While this has certain uses, it is not the most elegant or capable of smart pointer designs Provides the ability to: simulate the lifetime of a local variable or member variable for an object that is actually dynamically allocated provide a mechanism for "transfer of ownership" of objects from one owner to another Example #include <memory> // for std::auto_ptr #include <iostream> class Simple { public: std::auto_ptr<int> theInt; Simple() : theInt(new int( )) { *theInt = 3; //get object like normal pointer } int f( ) { return 42; } // when this class is destroyed, theInt will // automatically be freed }; int main( ) { std::auto_ptr<Simple> simple(new Simple( )); // access member functions like normal pointers std::cout << simple->f( ); // the Simple object is freed when simple goes out of scope return 0; Creating Your Own Smart Pointer Type One of the rationale of using smart pointers is to avoid leaking memory In order to avoid this, we should avoid manually managing heap-base memory We have to find a container which can automatically return the memory back to the operation system when we do not use it Creating Your Own Smart Pointer Type The destructor of class can match this requirement. What we need to store in a basic smart pointer is, of course, the address of the allocated memory For this, we can simply use a pointer Creating Your Own Smart Pointer Type Assume we are executing a design for storing a piece of memory for an int class smt_ptr { private: int* ptr; }; Creating Your Own Smart Pointer Type In order to make sure that every user puts an address in this smart pointer when doing initialization, we have to specify the constructor to accept a declaration of this smart pointer with the target address as the argument, but not "mere declaration" of the smart pointer itself class smt_ptr { public: explicit smt_ptr(int* pointer) : ptr(pointer) { } private: What do these int* ptr; mean? }; Creating Your Own Smart Pointer Type Now, we have to specify the class to "delete" the pointer when the instance of this smart pointer destructs class smt_ptr { public: explicit smt_ptr(int* pointer) : ptr(pointer) { } ~smt_ptr() { delete ptr; } private: int* ptr; }; Creating Your Own Smart Pointer Type We have to allow users to access the data stored in this smart pointer and make it more “pointerlike” For this, we may add a function to provide the access the raw pointer, and overload some operators, (such as operator* and operator->) to make it behave like a real pointer Creating Your Own Smart Pointer Type class smt_ptr { public: explicit smt_ptr(int* pointer) : ptr(pointer) { } // Declares these functions const to indicate that // there is no modification to the data members. ~smt_ptr() { delete ptr; } int* get() const { return ptr; } int* operator->() const { return ptr; } int& operator*() const { return *ptr; } private: int* ptr; }; Final Thought However, to make this "homemade" smart pointer work with other data types and classes, we have to turn it into a class template But Templates is a topic for CS171