Contents
Containers
Containers contain collection of objects. We have several kinds such as vector, map, queue, list. Advantage of these over arrays is that they can grow automatically and memory is taken care of internally.
File: v1.cpp
#include <iostream> #include <vector> using namespace std; int main() { // create a vector to store int vector<int> vec; int i1; // display the original size of vec cout << "vector size = " << vec.size() << ":" << vec.capacity() << endl; // push 5 values into the vector for(i1 = 0; i1 < 5; i1++) { vec.push_back(i1); } // display extended size of vec cout << "extended vector size = " << vec.size() << ":" << vec.capacity() << endl; // access 5 values from the vector for(i1 = 0; i1 < 5; i1++) { cout << "value of vec [" << i1 << "] = " << vec[i1] << endl; } // use iterator to access the values vector<int>::iterator iter = vec.begin(); while( iter != vec.end()) { cout << "value of v = " << *iter << endl; iter++; } //use range loop for( int x1 : vec ) cout << x1 << " " ; cout << endl ; return 0; }
vector size = 0:0 extended vector size = 5:8 value of vec [0] = 0 value of vec [1] = 1 value of vec [2] = 2 value of vec [3] = 3 value of vec [4] = 4 value of v = 0 value of v = 1 value of v = 2 value of v = 3 value of v = 4 0 1 2 3 4We use the "push_back" function to push the object to the end of the vector. The STL containers make copy of what we push to place in the container. If we look at the definition of the "push_back" call it looks like:
void push_back (const value_type& val);
Even though it seems to be taking a reference the STL eventually makes a copy somewhere.
File: v2.cpp
#include <iostream> #include <vector> using namespace std; class student { public: int id ; student( int idP ) { id = idP ; cout << "Constructor called for ;" << id << endl ; } student( const student& obj1 ) { id = obj1.id ; cout << "Copy Constructor called for ;" << id << endl ; } ~student( ) { cout << "Destructor called for ;" << id << endl ; } }; int main() { // create a vector to store int vector<student> vec; int i1; student studentObj( 1 ) ; // display the original size of vec cout << "vector size = " << vec.size() << endl; vec.push_back( studentObj ); // display extended size of vec cout << "extended vector size = " << vec.size() << endl; cout << "value of vec [" << 0 << "] = " << vec[0].id << endl; return 0; }
$ g++ v2.cpp ; ./a.exe Constructor called for ;1 vector size = 0 Copy Constructor called for ;1 extended vector size = 1 value of vec [0] = 1 Destructor called for ;1 Destructor called for ;1If we do not want a copy then we can use pointers.
File: v3.cpp
#include <iostream> #include <vector> using namespace std; //Use pointers to avoid a copy class student { public: int id ; student( int idP ) { id = idP ; cout << "Constructor called for ;" << id << endl ; } student( const student& obj1 ) { id = obj1.id ; cout << "Copy Constructor called for ;" << id << endl ; } ~student( ) { cout << "Destructor called for ;" << id << endl ; } }; int main() { // create a vector to store int vector<student*> vec; int i1; //student studentObj( 1 ) ; student* ptr ; ptr = new student( 1 ) ; // display the original size of vec cout << "vector size = " << vec.size() << endl; // push 5 values into the vector vec.push_back( ptr ); // display extended size of vec cout << "extended vector size = " << vec.size() << endl; cout << "value of vec [" << 0 << "] = " << vec[0]->id << endl; delete ptr ; return 0; }This works but there are a couple of problems with this approach. We must remember to delete the pointer but only once and there is another copy in the vector object. If we delete the pointer first and then somewhere in the code the vector is used to access the object then the program does not work correctly.
File: v4.cpp
#include <iostream> #include <vector> using namespace std; //Use pointers to avoid a copy class student { public: int id ; student( int idP ) { id = idP ; cout << "Constructor called for ;" << id << endl ; } student( const student& obj1 ) { id = obj1.id ; cout << "Copy Constructor called for ;" << id << endl ; } ~student( ) { cout << "Destructor called for ;" << id << endl ; } }; int main() { // create a vector to store int vector<student*> vec; int i1; //student studentObj( 1 ) ; student* ptr ; ptr = new student( 1 ) ; // display the original size of vec cout << "vector size = " << vec.size() << endl; // push 5 values into the vector vec.push_back( ptr ); // display extended size of vec cout << "extended vector size = " << vec.size() << endl; // access 5 values from the vector cout << "value of vec [" << 0 << "] = " << vec[0]->id << endl; // use iterator to access the values delete ptr ; //Using the vector after the object has been deleted. cout << "value of vec [" << 0 << "] = " << vec[0]->id << endl; return 0; }
$ g++ v4.cpp ; ./a.exe Constructor called for ;1 vector size = 0 extended vector size = 1 value of vec [0] = 1 Destructor called for ;1 value of vec [0] = -631185240How do we take care of this problem. It's smart pointers to the rescue. A smart pointer is a wrapper around the raw pointer and takes care of certain details for us. Before we jump into smart pointers; let's review some of the basics.
File: ptr1.cpp
#include <iostream> #include <string> using namespace std ; int main() { int* ptr1 ; int* ptr2 ; ptr1 = new int ; *ptr1 = 11 ; ptr2 = ptr1 ; delete ptr2 ; //not valid cout << *ptr1 << endl ; int* ptr3 ; int* ptr4 ; ptr3 = new int ; *ptr3 = 12 ; ptr4 = ptr3 ; delete ptr3 ; delete ptr4 ; cout << *ptr3 << endl ; } [amittal@hills cs110b]$ g++ ptr1.cpp ; ./a.out 6443 ----------- free(): double free detected in tcache 2 Aborted (core dumped) ptr2 = ptr1 ; In the above line the "ptr2" has the same address as "ptr1" . If we delete one then we cannot use the other. We get the value "6443" instead of the original value of "11" . Also we cannot do: ptr4 = ptr3 ; delete ptr3 ; delete ptr4 ; That is delete the same memory twice.
File: vector_destruct1.cpp
#include <iostream> #include <vector> using namespace std; //Use pointers to avoid a copy class student { public: int id ; student( ) { id=0 ; } student( int idP ) { id = idP ; cout << "Constructor called for ;" << id << endl ; } student( const student& obj1 ) { id = obj1.id ; cout << "Copy Constructor called for ;" << id << endl ; } ~student( ) { cout << "Destructor called for ;" << id << endl ; } }; int main() { // create a vector to store int student studentObj1( 1 ) ; student studentObj2( 2 ) ; student studentObj3( 3 ) ; cout << "start of vector." << endl ; { vector<student> vec() ; vec.push_back( studentObj1 ); vec.push_back( studentObj2 ); vec.push_back( studentObj3 ); } cout << "Done with vector." << endl ; return 0; } $ g++ vector_destruct1.cpp ; ./a.exe Constructor called for ;1 Constructor called for ;2 Constructor called for ;3 start of vector. Copy Constructor called for ;1 Copy Constructor called for ;2 Copy Constructor called for ;1 Destructor called for ;1 Copy Constructor called for ;3 Copy Constructor called for ;1 Copy Constructor called for ;2 Destructor called for ;1 Destructor called for ;2 Destructor called for ;1 Destructor called for ;2 Destructor called for ;3 Done with vector. Destructor called for ;3 Destructor called for ;2 Destructor called for ;1 Deller@DESKTOP-DNEP4MC /cygdrive/c/WebSite/Learn/2/cplusplus/stl $We want a wrapper for our pointer so that the pointer is deleted automatically.
File: custom1.cpp
#include <iostream> using namespace std ; class student { public: int id ; student( ) { id=0 ; } student( int idP ) { id = idP ; cout << "Constructor called for ;" << id << endl ; } student( const student& obj1 ) { id = obj1.id ; cout << "Copy Constructor called for ;" << id << endl ; } ~student( ) { cout << "Destructor called for ;" << id << endl ; } }; template < typename T > class SP { private: T* pData; // Generic pointer to be stored public: SP(T* pValue) : pData(pValue) { } ~SP() { delete pData; } T& operator* () { return *pData; } T* operator-> () { return pData; } }; int main() { SP< student > per1(new student(1)); cout << per1->id << endl ; cout << (*per1).id << endl ; // Dont need to delete student pointer.. student* ptr2 = new student(2) ; SP< student > per2( ptr2 ); cout << per2->id << endl ; cout << (*per2).id << endl ; return 0 ; } $ g++ custom1.cpp ; ./a.exe Constructor called for ;1 1 1 Constructor called for ;2 2 2 Destructor called for ;2 Destructor called for ;1This is better but has an issue when we copy our smart pointer.
File: custom2.cpp
#include <iostream> using namespace std ; class student { public: int id ; student( ) { id=0 ; } student( int idP ) { id = idP ; cout << "Constructor called for ;" << id << endl ; } student( const student& obj1 ) { id = obj1.id ; cout << "Copy Constructor called for ;" << id << endl ; } ~student( ) { cout << "Destructor called for ;" << id << endl ; } }; template < typename T > class SP { private: T* pData; // Generic pointer to be stored public: SP(T* pValue) : pData(pValue) { } ~SP() { delete pData; } T& operator* () { return *pData; } T* operator-> () { return pData; } }; int main() { SP< student > per1(new student(1)); cout << per1->id << endl ; cout << (*per1).id << endl ; //Make a copy SP< student > per2 = per1 ; return 0 ; } $ g++ custom2.cpp ; ./a.exe Constructor called for ;1 1 1 Destructor called for ;1 Destructor called for ;-1486954328 Aborted (core dumped)We want to make copies of our smart pointer class but only delete the pointer once.
File: custom3.cpp
#include <iostream> using namespace std ; class student { public: int id ; student( ) { id=0 ; } student( int idP ) { id = idP ; cout << "Constructor called for ;" << id << endl ; } student( const student& obj1 ) { id = obj1.id ; cout << "Copy Constructor called for ;" << id << endl ; } ~student( ) { cout << "Destructor called for ;" << id << endl ; } }; template < typename T > class SP { private: T* pData; // Generic pointer to be stored int* referenceCount ; public: SP(T* pValue) : pData(pValue) { referenceCount = new int ; *referenceCount = 1 ; } SP( const SP& anotherSP_object ) { cout << "Copy constructor getting called. " << endl ; pData = anotherSP_object.pData ; referenceCount = anotherSP_object.referenceCount ; //Updates it in all the copies *referenceCount = *referenceCount + 1 ; } ~SP() { (*referenceCount)-- ; cout << "SP Destructor referenceCount: " << *referenceCount << endl ; if ( *referenceCount == 0 ) { delete pData ; delete referenceCount ; } } T& operator* () { return *pData; } T* operator-> () { return pData; } }; int main() { SP< student > per1(new student(1)); cout << per1->id << endl ; cout << (*per1).id << endl ; //Make a copy SP< student > per2 = per1 ; return 0 ; }
Exercise
File: custom_ex1.cpp
Solution
File: custom_ex1_s.cpp
#include <iostream> #include <vector> using namespace std ; class student { public: int id ; student( ) { id=0 ; } student( int idP ) { id = idP ; cout << "Constructor called for ;" << id << endl ; } student( const student& obj1 ) { id = obj1.id ; cout << "Copy Constructor called for ;" << id << endl ; } ~student( ) { cout << "Destructor called for ;" << id << endl ; } }; template < typename T > class SP { private: T* pData; // Generic pointer to be stored int* referenceCount ; public: SP(T* pValue) : pData(pValue) { referenceCount = new int ; *referenceCount = 1 ; } SP( const SP& anotherSP_object ) { cout << "Copy constructor getting called. " << endl ; pData = anotherSP_object.pData ; referenceCount = anotherSP_object.referenceCount ; //Updates it in all the copies *referenceCount = *referenceCount + 1 ; } ~SP() { (*referenceCount)-- ; cout << "SP Destructor for:" << pData->id << " referenceCount: " << *referenceCount << endl ; if ( *referenceCount == 0 ) { delete pData ; delete referenceCount ; } } T& operator* () { return *pData; } T* operator-> () { return pData; } }; int main() { student* ptr1 = new student(1) ; SP< student > SmartPointer1( ptr1 ); //Create another SP object to hold a new student with //id 2 //Create a vector to hold SP objects. // Push the 2 SP objects onto the vector //Observe the output . It should only show 2 destructors return 0 ; }
Smart Pointer
We have 2 different kinds of smart pointers with C++: unique pointer and shared pointer. A unique pointer only allows one variable and does not allow copying of that variable.Unique Pointer
File: smart1.cpp
#include <iostream> #include <memory> #include <utility> using namespace std; //unique_ptr class student { public: int id ; student( int idP ) { id = idP ; cout << "Constructor called for ;" << id << endl ; } student( const student& obj1 ) { id = obj1.id ; cout << "Copy Constructor called for ;" << id << endl ; } ~student( ) { cout << "Destructor called for ;" << id << endl ; } }; int main() { std::unique_ptr<student> valuePtr( new student(1) ) ; cout << "Student id:" << (*valuePtr).id << endl ; }The actual pointer is encapsulated in the object with the line:
std::unique_ptr
When the object "valuePtr: goes out of scope then it will call a delete on the pointer. We can use the "make_unique" function to help create the pointer also.
File: smart2.cpp
#include <iostream> #include <memory> #include <utility> using namespace std; //unique_ptr class student { public: int id ; student( int idP ) { id = idP ; cout << "Constructor called for ;" << id << endl ; } student( const student& obj1 ) { id = obj1.id ; cout << "Copy Constructor called for ;" << id << endl ; } ~student( ) { cout << "Destructor called for ;" << id << endl ; } }; void function1( unique_ptr<student> ptr ) { } int main() { std::unique_ptr<student> valuePtr( new student(1) ) ; cout << "Student id:" << (*valuePtr).id << endl ; //Compiler error . Cannot make //function1( valuePtr ) ; //Using the make_unique function std::unique_ptr<student> valuePtr1 = make_unique< student >(2) ; cout << "Student id:" << (*valuePtr1).id << endl ; }The "shared_ptr" allows us to make copies of the object. It keeps a referece count that is incremented every time the object is copied. That way the "delete" is only called once.
Shared Pointer
File: smart3.cpp
#include <iostream> #include <memory> #include <utility> using namespace std; //unique_ptr class student { public: int id ; student( int idP ) { id = idP ; cout << "Constructor called for ;" << id << endl ; } student( const student& obj1 ) { id = obj1.id ; cout << "Copy Constructor called for ;" << id << endl ; } ~student( ) { cout << "Destructor called for ;" << id << endl ; } }; void function1( shared_ptr<student> ptr ) { cout << "Inside function Reference count:" << ptr.use_count() << endl ; } int main() { shared_ptr<student> valuePtr( new student(1) ) ; cout << "Student id:" << (*valuePtr).id << endl ; // cout << "Before function Reference count:" << valuePtr.use_count() << endl ; function1( valuePtr ) ; cout << "Outside function Reference count:" << valuePtr.use_count() << endl ; //Using the make_shared function shared_ptr<student> valuePtr1 = make_shared< student >(2) ; cout << "Student id:" << (*valuePtr1).id << endl ; //Reference countins is used so that the destructor is //called only once. shared_ptr<student> valuePtr2 = valuePtr ; cout << "Reference count:" << valuePtr2.use_count() << endl ; function1( valuePtr ) ; } $ g++ smart3.cpp ; ./a.exe Constructor called for ;1 Student id:1 Before function Reference count:1 Inside function Reference count:2 Outside function Reference count:1 Constructor called for ;2 Student id:2 Reference count:2 Inside function Reference count:3 Destructor called for ;2 Destructor called for ;1We can use the "shared_ptr" in our vector example.
File: v5.cpp
#include <iostream> #include <vector> #include <memory> using namespace std; //Use pointers to avoid a copy class student { public: int id ; student( int idP ) { id = idP ; cout << "Constructor called for ;" << id << endl ; } student( const student& obj1 ) { id = obj1.id ; cout << "Copy Constructor called for ;" << id << endl ; } ~student( ) { cout << "Destructor called for ;" << id << endl ; } }; int main() { // create a vector to store int vector< shared_ptr<student> > vec; { shared_ptr<student> valuePtr( new student(1) ) ; // display the original size of vec cout << "vector size = " << vec.size() << endl; // push 5 values into the vector vec.push_back( valuePtr ); //valuePtr gets destroyed here but //delete is not called for valuePtr } cout << "value of vec [" << 0 << "] = " << vec[0]->id << endl; return 0; }There is nothing special about the smart pointer. It is a class that someone wrote. We can write our own simple smart pointer.
File: smart5.cpp
#include <iostream> using namespace std ; class Person { int age; const char* pName; public: Person(): pName(0),age(0) { } Person(const char* pName, int age): pName(pName), age(age) { } ~Person() { cout << "Person Destructor." << endl ; } void Display() { printf("Name = %s Age = %d \n", pName, age); } }; template < typename T > class SP { private: T* pData; // Generic pointer to be stored public: SP(T* pValue) : pData(pValue) { } ~SP() { delete pData; } T& operator* () { return *pData; } T* operator-> () { return pData; } }; int main() { SP< Person > p(new Person("Scott", 25)); p->Display(); // Dont need to delete Person pointer.. return 0 ; }
File: v6.cpp
#include <iostream> #include <memory> #include <utility> #include <vector> using namespace std; int main() { vector<int> v1 ; v1.push_back(1) ;v1.push_back(2) ;v1.push_back(3) ; v1.push_back(4) ;v1.push_back(5) ;v1.push_back(6) ; v1.erase( v1.begin() + 1 ) ; for( int x1 : v1 ) cout << x1 << " " ; cout << endl ; // v1.begin() + 1 to one less v1.begin() + 3 v1.erase( v1.begin() + 1, v1.begin() + 3 ) ; for( int x1 : v1 ) cout << x1 << " " ; cout << endl ; } $ g++ v6.cpp ; ./a.exe 1 3 4 5 6 1 5 6
Exercise
1)File: vector_ex1.cpp
// C++ program to demonstrate the use of list containers #include <iostream> #include <list> #include <vector> using namespace std; int main() { vector< vector<int> > vectorOfVectors ; vector<int> v1 ; v1.push_back( 1 ) ; v1.push_back( 2 ) ; v1.push_back( 3 ) ; vectorOfVectors.push_back( v1 ) ; vector<int> v2 ; v2.push_back( 4 ) ; v2.push_back( 5 ) ; v2.push_back( 6 ) ; vectorOfVectors.push_back( v2 ) ; vector<int> v3 ; v3.push_back( 7 ) ; v3.push_back( 8 ) ; v3.push_back( 9 ) ; vectorOfVectors.push_back( v3 ) ; //TO DO //Print all the elements from vectorOfVectors //Do not hardcode any values return 0 ; }2) What does the below program print. Explain.
File: vector_ex2.cpp
1) File: vector_ex1s.cpp
2) 1 2 3 4 5 6 v1.erase(v1.begin() + 2) ; 1 2 4 5 6 v1.insert(v1.begin() + 1, 10 ) ; 1 10 2 4 5 6 vectorv2 = { 20, 50 , 60 } ; v1.insert(v1.begin() + 1, v2.begin(), v2.end()); 1 20, 50, 60, 10 , 2, 4, 5, 6