Presentation is loading. Please wait.

Presentation is loading. Please wait.

Resource Allocation and Ownership

Similar presentations


Presentation on theme: "Resource Allocation and Ownership"— Presentation transcript:

1 Resource Allocation and Ownership
Lecture 4 Resource Allocation and Ownership

2 Operator Overloading in C++
Definition: An overloaded operator is called an operator function Declaration: An operator function is declared with the keyword operator preceding the operator symbol You can overload all of the following operators:

3 Binary Operator Overloading Example
#include<iostream> class Complex { private: int real, imaginary; public: Complex(int r = 0, int i =0) { real = r; imaginary = i; } Complex operator + (Complex const &other) Complex result; //create a result object result.real = real + other.real; //add this with other result.imaginary = imaginary + other.imaginary; return res; //return result object, without affecting this nor other void print() { cout << real << " + i" << imaginary << endl; } }; int main() { Complex c1(10, 5), c2(2, 4); Complex c3 = c1 + c2; c3.print(); // what would this print?

4 Resources Objects sometimes acquire resources.
When allocated (usually). When an Object is destructed, its resource MUST be released. A resource: anything that follows the Release/Acquire protocol. Files (open/close), memory (new/delete).

5 Construction of an Object
Construction of an object is a process of: Allocating – memory to hold the object’s instance. Initialization – initial state of the object. Acquiring resources. Consistent state: making sure that the initial state is valid.

6 Construction of an Object
When a new object is instantiated The RTE allocates the needed memory and calls the constructor of the class. The object doesn’t exist until the construction is finished. In the constructor’s code: Do not access this. Do not throw exceptions.

7 Owning resources When an object is constructed, resources are allocated. These should be deallocated at some point. In C++ it is the programmer's responsibility to do so. The process of releasing the resources is called the destruction of the object.

8 Destruction of an object
Releasing resources: all the resources acquired by the object including all the allocated memory. An object is implicitly destructed if the object is allocated on the stack (when leaving scope). An object allocated on the heap is destructed by the programmer. by deleteing the pointer to the object.

9 Destruction of an object
Performed in this order: The RTE calls a special function of the object, namely, the destructor. The RTE releases the memory used to hold the object's state (either from the stack, or the memory blocked on the heap returned by new).

10 Example: The IntArray object holds an array of ints.
The array is dynamically allocated by the object during its construction. The array is “owned” by the object. It is the object responsibility to manage the memory of this array. Where is the memory released???

11 The following code will create a memory leak of 30 ints.

12 The Rule of 3 The rule of 3 is a rule of thumb in C++ (prior to C++11). Each class that owns a resource must explicitly define all three: destructor copy constructor (copy) assignment operator

13 Destructors The class that owns the array is responsible for releasing it. We need to add a destructor to IntArray:

14 Destructors virtual: “overridable”
The class that owns the array is responsible for releasing it. We need to add a destructor to IntArray: virtual: “overridable” A destructor should always be declared virtual. Declare each method virtual (unless there’s a reason why not)

15 Copy constructor Receives a single argument of the same type and copies the argument’s values into this. Default copy c’tor: shallow copying.

16 Copy Assignment operator
Activated when someone attempts to assign a new value to an already initialized value. Default: shallow copying.

17 (Bad) example Memory Addresses Aliasing - an object leaks the pointers to its resources to other objects. Happens in line 3 of test(). After line 4, both x and y internal arrays may point to already freed values.

18 The “right” operators

19 Another example

20 Another example’s output

21 Move semantics - motivation
IntArray return_int_array() { ... } void accept_int_array(IntArray array) { ... } void test() {   accept_int_array(return_int_array());    IntArray x(10);   x = return_int_array(); } return_int_array creates an int_array and returns it. this int_array is kept to a temporary location (it is an r-value). To pass it to accept_int_array c++ creates a copy of the temporary variable using the copy constructor. When the function returns, c++ will destruct both the temporary array and the temporary copy. This is inefficient!! The third line of test demonstrates the same process only now with the copy assignment operator.

22 Move semantics - motivation
IntArray return_int_array() { ... } void accept_int_array(IntArray array) { ... } void test() {   accept_int_array(return_int_array());    IntArray x(10);   x = return_int_array(); } return_int_array creates an int_array and returns it. this int_array is kept to a temporary location (it is an r-value). To pass it to accept_int_array c++ creates a copy of the temporary variable using the copy constructor. When the function returns, c++ will destruct both the temporary array and the temporary copy. This is inefficient!! The third line of test demonstrates the same process only now with the copy assignment operator. Can we do better? Yes! using C++'s rvalue references. (Since 2011)

23 rvalue references References to rvalue
Defined using &&, i.e., int&& i; Allows us to create functions that use temporary variables without copying them An rvalue reference T&& is a new kind of reference that only binds to rvalues Functions receiving rvalue references can “steal” resources from the referenced object The referenced object itself will be destructed once the method completes executing. This happens because they are rvalue references without any variable to keep them alive

24 The Rule of Five A class containing resources must define the following: A destructor A copy constructor A move constructor A copy assignment operator A move assignment operator This new version of the rule of 3 is needed due to: The addition of rvalue references The attempt to improve the performance of cloning objects

25 move constructor / assignment operator
Acts when a temporary variable is sent to the copy constructor/assignment operator We know that this variable can never be referenced again! We can just "steal" or "move" its resources instead of fully copying them. To move an object means to transfer ownership of some resource it manages to another object This allows turning expensive copies into cheap moves

26

27 C++ Templates Foundation of generic programming in C++, just like Java Generics. A way to tell the compiler how to generate a class or a function given some compile time variables: std::vector<int> or std::vector<std::string> Three types of templates in C++: Function Template – makes functions generic Class Templates – makes classes generic

28 Function Templates Syntax:
Use these types in the function declaration to declare generic variables #include <iostream> #include <string> template <typename T> T const& Max (T const& a, T const& b) { return a < b ? b:a; } int main () { int i = 39; int j = 20; std::cout << "Max(i, j)= " << Max(i, j) << std::endl;

29 Class Templates: syntax example
//declarations: calculator.hpp file template <typename E> class calculator{ public: E multiply(E x, E y); E add(E x, E y); }; //implementations: calculator.cpp file template <typename E> E calculator<E>::multiply(E x, E y){ return x * y; } template <typename E> E calculator<E>::add(E x, E y){ return x + y;

30

31 RAII - Resource Acquisition Is Initialization
Binding the life cycle of a resource to the lifetime of an object. Simply: encapsulate each resource that must be acquired before use into a class Acquiring the resource is done in the constructor Sanity checks are done in the constructor, and exceptions are thrown in case of errors Releasing the resource is done by the destructor Due to the sanity checks in constructor, no exceptions are thrown in destructor Benefits To guarantee release of resources at the end of a scope

32 Resources That Need Acquiring
To use the resource, make an instance of the class! This guarantees that the resource is available when accessing the object instance This guarantees that all resources are released when the lifetime of their controlling object ends Examples of RAII classes: std::string, std::vector, std::thread They acquire their resources in constructors They release them in their destructors They don't require explicit cleanup! Resources that need acquiring before use and suitable for this paradigm: Heap memory, file handles, network sockets, database handles

33 Smart Pointers A class template declared on the stack, and initialized by a raw pointer that points to a heap-allocated object or other resource Smart pointers are responsible for deleting the memory that the raw pointer specifies, which is done in its destructor Accessing the encapsulated pointer by using an overloaded version of the operator-> and operator* C++ Smart Pointers collection: unique_ptr(C++11), shared_ptr(C++11), weak_ptr(C++11), auto_ptr(until C++17), owner_less(C++11), enable_shared_from_this(C++11), bad_weak_ptr(C++11), default_delete(C++11)

34 Smart Pointer using Reference Counting
Reference counting is used to count the number of references found in the program to the smart pointer data Each time the copy constructor of the smart pointer is executed, the reference counter is increased by one Due to executing the copy constructor another smart pointer object will be created on some stack frame of some function Each time the destructor of the smart pointer is executed, the reference counter is decreased by one This means some function has exited, deleting its corresponding stack frame with it If the reference counter value is one and the destructor is executed then the data will be removed from the heap Effectively deleting the smart pointer and its data from the program!

35 Smart Pointer using Reference Counter Example
In this implementation we will use three auxiliary functions: link: Increases reference counter by one Used in both copy constructor and copy assignment operator steal: Moves the data from other smart pointer to this smart pointer, Used in both move constructor and move assignment operator clean: Will either reduce the reference counter by one or delete the smart pointer data Used in copy assignment operator, move assignment operator, destructor Then we will implement the rule of five

36 SmartPointer.hpp - Declarations
#include <iostream> template <typename T> class SmartPointer { private: T *data; int *referenceCounter; void link(const SmartPointer<T>& other); void steal(SmartPointer<T>& other); void clean(); public: SmartPointer(T *data); SmartPointer(const SmartPointer<T>& other); SmartPointer<T>& operator=(const SmartPointer<T>& other); SmartPointer(SmartPointer<T>&& other); SmartPointer& operator=(SmartPointer<T>&& other); ~SmartPointer(); int getReferenceCounter(); T& get(); void set(const T &data); void inc(SmartPointer<int> number); };

37 SmartPointer.cpp: link, steal, clean
void link(const SmartPointer<T>& other) { this->data = other.data; this->referenceCounter = other.referenceCounter; *referenceCounter+=1; } void steal(SmartPointer<T>& other) { other.data = nullptr; other.referenceCounter = nullptr; void clean() { if (referenceCounter==nullptr) return; else if (*referenceCounter == 1){ delete this->data; delete this->referenceCounter; this->data = nullptr; this->referenceCounter = nullptr; }else *referenceCounter -= 1;

38 SmartPointer.cpp – Rule of Five
11 SmartPointer(T *data) { //constructor – smart pointer initializer this->data = data; this->referenceCounter = new int(1); } SmartPointer(const SmartPointer<T>& other) { //copy constructor link(other); SmartPointer<T>& operator=(const SmartPointer<T>& other) { //copy assignment operator if (&other != this) { clean(); return *this;

39 SmartPointer.cpp – Rule of Five - Continued
SmartPointer(SmartPointer<T>&& other) { //move constructor steal(other); } SmartPointer& operator=(SmartPointer<T>&& other) { //move assignment operator clean(); return *this; ~SmartPointer(){ //destructor

40 SmartPointer.cpp – Other auxiliary functions
int getReferenceCounter(){ return *(this->referenceCounter); } T& get() { return *(this->data); void set(const T &data) { *(this->data) = data; void inc(SmartPointer<int> number) { std::cout << "[inc]pointer value= " << number.get() << " reference count= " << number.getReferenceCounter() << std::endl; number.set(number.get() + 1);

41 SmartPointer.cpp – main and execution
int main() { SmartPointer<int> number(new int(42)); std::cout << "[main]pointer value= " << number.get() << " reference count= " << number.getReferenceCounter() << std::endl; inc(number); std::cout << "[main]and now the pointer value is " << number.get() << } Output? Line 03: [main]pointer value= 42 reference count= 1 Line 12: [inc]pointer value= 42 reference count= 2 Line 16: [inc]pointer value= 43 reference count= 2 Line 07: [main]and now the pointer value is 43 reference count= 1

42 C++ Smart Pointers: std::shared_ptr
std::shared_ptr is designed for scenarios in which more than one owner might want to manage the lifetime of the object in memory. It uses reference counter to keep track of these copies! After std::shared_ptr is initialized: It can be copied, passed by value to functions, and assigned to other std::shared_ptr instances All the instances point to the same data, and able to access the shared data simultaneously Once all copies of the std::shared_ptr are removed, the data will be removed from heap automatically The user need not handle memory deallocation of the objects!

43 C++ Smart Pointers: std::shared_ptr
Include: #include <memory> Syntax: std::shared_ptr<typename> ptrName(new typeName(values…)); Note that the smart pointer is created on the stack Its data however, are of a pointer form, created on the heap Usage: std::cout << ptrName //this will print the data pointer address std::cout << *ptrName //this will print the value of the data itself

44 std::shared_ptr – Code Example
#include <iostream> #include <memory> void increment(std::shared_ptr<int> intPtr){ std::cout << "[increment]value= " << *intPtr << " referenceCount= " << intPtr.use_count() << std::endl; *intPtr = 6; " referenceCount= " << intPtr.use_count() << std::endl; } int main(){ std::shared_ptr<int> intPtr(new int(5)); std::cout << "[main]value= " << *intPtr << increment(intPtr); return 0; } What’s the output of lines 13? 5? 8? 16?

45 std::shared_ptr – Code Example
#include <iostream> #include <memory> void increment(std::shared_ptr<int> intPtr){ std::cout << "[increment]value= " << *intPtr << " referenceCount= " << intPtr.use_count() << std::endl; *intPtr = 6; " referenceCount= " << intPtr.use_count() << std::endl; } int main(){ std::shared_ptr<int> intPtr(new int(5)); std::cout << "[main]value= " << *intPtr << increment(intPtr); return 0; } What’s the output of lines 13? 5? 8? 16? [main]value= 5 referenceCount= 1 [increment]value= 5 referenceCount= 2 [increment]value= 6 referenceCount= 2 [main]value= 6 referenceCount= 1  

46 C++ Smart Pointers: std::unique_ptr
std::unique_ptr is the lightweight version of std::shared_ptr In cases where you need one reference only to a dynamically allocated data The unique pointer owns an object exclusively Lighter, since it has no reference counter! This means it cannot be sent to functions! This is because there cannot be multiple copies of the same pointer In practice, no copy constructor and assignment operator!

47 std::unique_ptr – Code Example
#include <iostream> #include <memory> void increment(std::unique_ptr<int> intPtr){ std::cout << "[increment]value= " << *intPtr<< std::endl; *intPtr = 6; } int main(){ std::unique_ptr<int> intPtr(new int(5)); std::cout << "[main]value= " << *intPtr<< std::endl; //increment(intPtr); increment(std::move(intPtr)); if (intPtr.get() != nullptr) std::cout << "[main]value= " << *intPtr << std::endl; else std::cout << "[main]intPtr is empty!" << std::endl; return 0; Will line 13 compile? What did we do in line 14? Will the if be executed, or the else clause?

48 std::unique_ptr – Code Example
#include <iostream> #include <memory> void increment(std::unique_ptr<int> intPtr){ std::cout << "[increment]value= " << *intPtr<< std::endl; *intPtr = 6; } int main(){ std::unique_ptr<int> intPtr(new int(5)); std::cout << "[main]value= " << *intPtr<< std::endl; //increment(intPtr); increment(std::move(intPtr)); if (intPtr.get() != nullptr) std::cout << "[main]value= " << *intPtr << std::endl; else std::cout << "[main]intPtr is empty!" << std::endl; return 0; Will line 13 compile? What did we do in line 14? Will the if be executed, or the else clause? [main]value= 5 [increment]value= 5 [increment]value= 6 [main]intPtr is empty!  


Download ppt "Resource Allocation and Ownership"

Similar presentations


Ads by Google