[Do It Yourself] implement simple C ++ smart pointer

Source: Internet
Author: User

Why smart pointer?

Why do we need smart pointers? C ++ memory management has always been a headache.

Assume that we have the following person object: each person has its own name and can tell you its name:

////a person who can tell us his/her name.//#include<iostream>#include<string>using namespace std;class person{public:person(string);void tell();~person();private:string name;  };person::person(string name):name(name){}void person::tell(){cout << "Hi! I am " << name << endl;}person::~person(){cout << "Bye!" << endl;}

Most of the time, we don't know how many objects we want to create, so we need to dynamically create and destroy objects in the program running.-in this case, we will use the new operator in heap) allocate memory for the created object and use Delete to release the allocated memory. A simple example:

#include "person.h"int main(){person *p = new person("Cici");p -> tell();delete p;}

Program Execution result:

For a simple program, we certainly will not forget to release the objects allocated in the heap. However, when the program is complex, thousands of objects are dynamically created. If these objects are not used, they must be released in a timely manner. Otherwise, memory leakage (Memory Leak) may occur ). Memory leakage is one of the most common problems in C ++ programs. Because c ++ does not provide the function of automatic heap memory management, all these tasks are handed over to the programmer himself. The programmer must be responsible for the heap objects that he or she dynamically creates: destroy the objects in time when they do not need to be used. However, programmers are not omnipotent, So memory leakage is always a common bug in C ++ programs.

I have to mention that Java has added a garbage collection mechanism (garbage collection). JVM will manage unused objects and destroy them in time, this greatly reduces the burden on programmers. Programmers only need new objects instead of releasing them, because when objects are not used (referenced, the garbage collector will clear the debris for us. Java is also widely concerned with this feature.

STL auto_ptr

C ++ programmers are also unwilling to be lonely. STL has the "smart pointer": STL: auto_ptr. With smart pointers, we don't have to worry about the release of objects, because smart pointers can help us release the object's memory space. With auto_ptr, our program can write as follows:

#include "person.h"#include<memory>using namespace std;int main(){auto_ptr<person> p(new person("Cici"));p -> tell();//we don't have to delete p because smart pointer will handle this//delete p;}

Execute the program and output the following:

We can see that the output is exactly the same as our first version. Although we do not delete the object we created, we can see that it has been correctly analyzed before the program exits.

The simple smart_ptr uses the example above to see the usage of STL auto_ptr, so we can first summarize the following basic functions that should be available: 1. to automatically release the objects pointed to, 2, the "->" operator is overloaded. When using smart_ptr, we can use "->" like a normal pointer to access member 3 of the object to which it points, the "*" unreference operator is overloaded. Same as above, how can we automatically release objects? We know that for a local object, its lifecycle is the local scope of the object (usually between "{}" in the Program), and after the program runs out of the scope of the local object, these local objects will be automatically destroyed (the object's destructor will also be called ). Therefore, the smart_ptr can explicitly Delete the objects to which the Destructor points, so that the objects we point to will be released outside the scope of the smart_ptr. So we can implement our own smart_ptr in this way:
////our simple smart pointer//#include "person.h"class smart_ptr{public:smart_ptr(person* p);~smart_ptr();person& operator*();person* operator->();private:person *ptr;};smart_ptr::smart_ptr(person* p):ptr(p){}smart_ptr::~smart_ptr(){delete ptr;}person&  smart_ptr::operator*(){return *ptr;}person* smart_ptr::operator->(){return ptr;}

To test this simple smart_ptr:

#include "smart_ptr.h"using namespace std;int main(){smart_ptr p(new person("Cici"));p -> tell();//we don't have to delete p because smart pointer will handle this//delete p;}

Running result:

Haha, our smart_ptr is working normally. However, we can see that this smart_ptr has many disadvantages. Our smart pointer can only point to our person object, when we need him to point to a new object, isn't it necessary to write a new smart_ptr Based on Huludao? Obviously, this is C ++. Of course we have a more general method: Template. We can use the template so that our smart_ptr decides the object it points to during compilation, so the improved version:
////our simple smart pointer//template <typename T>class smart_ptr{public:smart_ptr(T* p);~smart_ptr();T& operator*();T* operator->();private:T* ptr;};template <typename T>smart_ptr<T>::smart_ptr(T* p):ptr(p){}template <typename T>smart_ptr<T>::~smart_ptr(){delete ptr;}template <typename T>T&  smart_ptr<T>::operator*(){return *ptr;}template <typename T>T* smart_ptr<T>::operator->(){return ptr;}

Reference count

Is our smart_ptr perfect? Take a look at the following situation:

#include "person.h"#include "smart_ptr.h"using namespace std;int main(){smart_ptr<person> p(new person("Cici"));p -> tell();{smart_ptr<person> q = p;q -> tell();}}

Run the following command:

Program error. It is easy to find the cause: after the program runs out of its own scope, our smart pointer Q releases the person object pointing to, and our pointer P is the same as the object pointed to by Q, therefore, when the program exits, it tries to release a released object again. Obviously, the classic segmentation fault exception occurs. Therefore, the "simple" smart_ptr is too "simple.

Is there any way to solve this problem? Think about the file reference counter in the operating system. The operating system maintains a "reference count" for each opened file. When multiple processes open a file at the same time, the system increases the reference count by 1 in turn. If a process closes a file, the file will not be immediately closed. The system only reduces the reference count by 1. When the reference count is 0, it indicates that no one has used the file, the system releases the file resources. Therefore, we can maintain a reference count for the objects pointed to by smart_ptr. When a new smart_ptr points to this object, we add the reference count to 1, when the smart_ptr is released, we only remove the reference count by one. When the reference count is reduced to 0, we can actually destroy the objects pointed to by the smart_ptr.

In addition, our previous smart_ptr still lacks a non-argument constructor, copying constructor and reloading the "=" operator. In our previous version, the = operator is not overloaded, but the program can still be executed normally. This is because the synthesis version of the compiler is used, this is also insecure (although no problem is found here ).

Okay, the following is the final version change (I added comments to the added part ):

////smart_ptr.h : our simple smart pointer//template <typename T>class smart_ptr{public://add a default constructorsmart_ptr();//smart_ptr(T* p);~smart_ptr();T& operator*();T* operator->();//add assignment operator and copy constructorsmart_ptr(const smart_ptr<T>& sp);smart_ptr<T>& operator=(const smart_ptr<T>& sp);//private:T* ptr;//add a pointer which points to our object's referenct counterint* ref_cnt;//};template <typename T>smart_ptr<T>::smart_ptr():ptr(0),ref_cnt(0){//create a ref_cnt here though we don't have any object to point toref_cnt = new int(0);(*ref_cnt)++;}template <typename T>smart_ptr<T>::smart_ptr(T* p):ptr(p){//we create a reference counter in heapref_cnt = new int(0);(*ref_cnt)++;}template <typename T>smart_ptr<T>::~smart_ptr(){//delete only if our ref count is 0if(--(*ref_cnt) == 0){delete ref_cnt;delete ptr;}}template <typename T>T&  smart_ptr<T>::operator*(){return *ptr;}template <typename T>T* smart_ptr<T>::operator->(){return ptr;}template <typename T>smart_ptr<T>::smart_ptr(const smart_ptr<T>& sp):ptr(sp.ptr),ref_cnt(sp.ref_cnt){(*ref_cnt)++;}template <typename T>smart_ptr<T>& smart_ptr<T>::operator=(const smart_ptr<T>& sp){if(&sp != this){//we shouldn't forget to handle the ref_cnt our smart_ptr previously pointed toif(--(*ref_cnt) == 0){delete ref_cnt;delete ptr;}//copy the ptr and ref_cnt and increment the ref_cntptr = sp.ptr;ref_cnt = sp. ref_cnt;(*ref_cnt)++;}return *this;}

To test all the functions of our smart_ptr, the test cases are also added here:

#include "person.h"#include "smart_ptr.h"using namespace std;int main(){smart_ptr<person> r;smart_ptr<person> p(new person("Cici"));p -> tell();{smart_ptr<person> q = p;q -> tell();r = q;smart_ptr<person> s(r);s -> tell();}r -> tell();}

The execution result is as follows:

We can see that our Cici object is destroyed only at the end, and our smart_ptr completes the task successfully.

PS:

Here, is our smart_ptr perfect? No.

For example, if our object is referenced by smart_ptr in multiple threads, there will be a problem with our smart_ptr, because the thread's mutex access to ref_cnt is not considered here, therefore, the reference count may be counted. We will not implement it here. After all, the smart_ptr here is just a "simple" implementation ~

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.