Download presentation
Presentation is loading. Please wait.
1
15 – Sequential Containers
4.3 Implementation of a Vector Class 4.4 The Copy Constructor, Assignment Operator, and Destructor 15 – Sequential Containers
2
Sequential Containers
3
Tip #12: new vs. malloc Differences between malloc and new:
Sequential Containers Differences between malloc and new: Calling Constructors: new calls constructors, malloc() does not. In fact primitive data types (char, int, float.. etc) can also be initialized with new. Operator vs function: new is an operator, malloc() is a function. Return type: new is type safe and returns exact data type, malloc() returns void *. Failure Condition: On failure, malloc() returns NULL, new Throws. Memory: In case of new, memory is allocated from free store where as in malloc() memory allocation is done from heap. Overriding: We are allowed to override new operator where as we can not override the malloc() function legally. Size: Required size of memory is calculated by compiler for new, where as we have to manually calculate size for malloc(). The short answer is: Fundamentally, malloc is a C feature and new is a C++ feature. Don't use malloc with C++ without a really good reason for doing so.
4
An Even Better TIP… We believe all that God has revealed, all that He does now reveal, and we believe that He will yet reveal many great and important things pertaining to the Kingdom of God. —Articles of Faith 1:9
5
4.3, pgs. 240-246 4.3 Implementation of a Vector Class
The Default Constructor The swap Function The Subscripting Operator The push_back Function The insert Function The erase Function The reserve Function Performance of the KW::Vector 4.3, pgs
6
A Vector Class Object Sequential Containers template<typename T> class Vector { private: // Data fields /** The initial capacity of the array */ static const size_t INITIAL_CAPACITY = 10; /** The current capacity of the array */ size_t current_capacity; /** The current num_items of the array */ size_t num_items; /** The array to contain the data */ T* the_data; public: // Member Functions ... }; We will implement a simplified version of the vector class. The physical size of the array is indicated by the data field current_capacity (type size_t because it must be non-negative) The number of data items stored is indicated by the data field num_items
7
Vector<int> myVector();
Sequential Containers template<typename T> class Vector { private: // Data fields ... public: // Member Functions /** Construct an empty vector w/default capacity */ Vector<T>() : current_capacity(INITIAL_CAPACITY), the_data(new T[INITIAL_CAPACITY]), num_items(0) {} }; Initializer List. current_capacity = INITIAL_CAPACITY; the_data = new T[INITIAL_CAPACITY]; num_items = 0;
8
myVector[1]; Sequential Containers template<typename T> class Vector { private: // Data fields ... public: // Member Functions /** Subscripting operator */ T& operator[](size_t index) // Verify valid index. if (index < 0 || index >= num_items) throw std::out_of_range("index to operator[] is out of range"); } return the_data[index]; }; Because this is a template class, all of the code must be in the header or in a file included by the header . In the STL vector, only the at function validates the index.
9
myVector.push_back(10);
Sequential Containers void push_back(const T& the_value) { // Make sure there is space for the new item. if (num_items == current_capacity) reserve(2 * current_capacity); /* Allocate an expanded array */ } // Insert the new item. the_data[num_items] = the_value; num_items++;
10
The reserve(size) Function
Sequential Containers void reserve(size_t new_capacity) { if (new_capacity > current_capacity) if (new_capacity > 2 * current_capacity) current_capacity = new_capacity; else current_capacity *= 2; // Double the capacity. T* new_data = new T[current_capacity]; for (size_t i = 0; i < num_items; i++) new_data[i] = the_data[i]; // Copy the data over. delete[] the_data; // Free old memory. the_data = new_data; // Point to the new data. } Doubling spreads out the cost of copying. Doubling an array of size n allows us to add n more items before another array copy. Therefore, we can add n new items after we have copied over n existing items. Although reallocation is O(n), we have to do a reallocation only after n items are added. This averages out to 1 copy per add, so reallocation is effectively an O(1) operation. push_back() is performed in amortized constant time.
11
myVector.insert(2, 20); void insert(size_t index, const T& the_value)
Sequential Containers void insert(size_t index, const T& the_value) { if (index > num_items) // Validate index. throw std::out_of_range("index to insert is out of range"); } if (num_items == current_capacity) // Check for room reserve(2 * current_capacity); // Allocate an expanded array for (size_t i = num_items; i > index; i--) // Open a slot the_data[i] = the_data[i - 1]; the_data[index] = the_value; // Insert new item num_items++;
12
myVector.erase(3); void erase(size_t index) { // Validate index.
Sequential Containers void erase(size_t index) { // Validate index. if (index > num_items) throw std::out_of_range ("index to erase is out of range"); } // Move items below the removed one up. for (size_t i = index + 1; i < num_items; i++) the_data[i - 1] = the_data[i]; num_items--;
13
Performance of Vector Sequential Containers The functions operator[] and at are each a few lines of code and contain no loops - execute in constant time, or O(1). If we insert into (or remove from) the middle of a vector, then at most n items have to be shifted which is O(n). What if we have to reallocate before we can insert? Recall that we spread out the cost of copying so that effectively it is an O(1) operation, so the insertion is still O(n) . Even if we don’t spread out the cost of copying, the copy operation is still O(n), so the worst case merely doubles the cost.
14
4.4 The Copy Constructor, Assignment Operator, and Destructor
Copying Objects and the Copy Constructor Shallow Copy versus Deep Copy Assignment Operator The Destructor 4.4, pgs
15
Copy Constructor Sequential Containers We want a copy of an object to be an independent copy, which means we should be able to modify one of the objects without affecting the other. Copying of primitive types is straightforward—the values are duplicated and placed in the target locations . For class types, copying is done by a class's copy constructor: MyClass(const MyClass& other); MyClass& operator=(const MyClass& other); The copy constructor is invoked automatically: when an object is passed to a function by value. when an object is returned from a function. when an object is initialized with another object of the same class. when the compiler generates a temporary object.
16
Default Copy Constructor
Sequential Containers A default constructor is a constructor which can be called without any arguments. Provided implicitly by the compiler - Any user defined constructor will prevent the compiler from implicitly declaring a default constructor. A copy constructor is a constructor which can be called with a single argument of the same type. Providing other constructors will not prevent the compiler from implicitly declaring a. The compiler calls copy constructors certain well defined contexts variable definitions and type conversions. To summarize, The compiler will provide an implicit copy constructor even if the class has other user defined constructors, provided none of those constructors can be considered copy constructors. And if you provide a user defined copy constructor, the compiler will not provide an implicitly declared default copy constructor.
17
Shallow Copy versus Deep Copy
Sequential Containers Referring back to our vector class, a copy of the size_t fields current_capacity and num_items creates independent copies of the variables. However, copying the pointer value the_data does not create an independent copy. Copying a pointer this way is considered a shallow copy. 1 2 3 4 5 Vector<int> v1 num_items = 5 current_capacity = 8 int* the_data = _____ Vector<int> v2 = v1; Vector<int> v2 num_items = 5 current_capacity = 8 int* the_data = _____
18
Deep Copy Sequential Containers We need to create an independent copy or a deep copy of the underlying array so that v1.the_data and v2.the_data point to different arrays, making vector v2 a deep copy of vector. /** Make a (deep) copy of a vector. @param other The vector to be copied */ Vector<T>(const Vector<T>& other) : current_capacity(other.capacity), num_items(other.num_items), the_data(new T[other.current_capacity]) { for (size_t i = 0; i < num_items; i++) the_data[i] = other.the_data[i]; }
19
Copying Objects To create an independent copy of an object, the
Sequential Containers To create an independent copy of an object, the Copy constructor is called when a new object is created from an existing object, as a copy of the existing object. Assignment operator is called when a already initialized object is assigned a new value from another existing object. MyClass t1, t2; MyClass t3 = t1; // copy constructor (t3 is new) t2 = t1; // assignment operator (t2 exists) t2.operator=(t1); // assignment operator (equivalent) Assignment of primitive types is straightforward—the value is duplicated and placed in the target location. Every class has a default assignment operator which makes a copy of each data field of the source and places it into the corresponding data field of the target . If a class type, that class’s assignment operator is used. The default assignment operator makes a shallow copy of an object, so we must override it if we want truly independent copies.
20
Assignment Operator Sequential Containers /** Assign the contents of one vector to another. @param other The vector to be assigned to this vector @return This vector with a copy of the other vector's contents */ Vector<T>& operator=(const vector<T>& other) { // Make a copy of the other vector. Vector<T> the_copy(other); // Swap contents of self with the copy. swap(the_copy); // Return self (the copy will be deleted) return *this; } Vector<int> v1(); Vector<int> v2(); v2 = v1; Upon return, the destructor for the temporary object (the_copy) is invoked automatically by the compiler, deleting the old data.
21
The Destructor Sequential Containers The purpose of the destructor is to undo what the constructor does. The constructor takes a block of uninitialized memory and sets it to a valid state, thus creating an object. When the destructor is finished, the object is in an invalid state and the memory it occupies can be reused for creating other objects. If this is not done, the program will have memory leaks. Each class has a default destructor, which effectively invokes the destructor for each data field. If the pointer references a dynamically allocated object (such as the array referenced by pointer the_data), the memory allocated to that object must be freed.
22
The std::swap Function
Sequential Containers The standard version of std::swap() will work for most types that are assignable: Template <typename T> void std::swap(T& lhs,T& rhs) { T temp(lhs); lhs = rhs; rhs = temp; } What’s wrong with using std:swap to swap vectors? std::swap(vector1, vector2); Answer: the above uses the vector deep copy constructors and assignment operators.
23
The swap Function Sequential Containers By adding your own version of swap() for your class you can implement an optimized shallow version of std::swap() avoiding the need to make copy of the whole data area, potentially release old data areas or re-allocate the data area, as well as invoke the copy constructor for the contained type on each item copied. /** Exchanges the contents of this vector with another. */ void swap(Vector<T>& other) { std::swap(num_items, other.num_items); std::swap(current_capacity, other.current_capacity); std::swap(the_data, other.the_data); }
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.