Implementing a simple smart pointer in C++
2015-10-06 17:27
405 查看
This article demonstrates how to implement a basic smart pointer in C++.
What are smart pointers? The answer is fairly simple; a smart pointer is a pointer which is smart. What does that mean? Actually, smart pointers are objects which behave like pointers but do more than a pointer. These objects are flexible as pointers and
have the advantage of being an object (like constructor and destructors called automatically). A smart pointer is designed to handle the problems caused by using normal pointers (hence called smart).
What are the common problems we face in C++ programs while using pointers? The answer is memory management. Have a look at the following code:
Hide Copy Code
How many times have we found a bug which was caused because we forgot to delete
could take care of releasing the memory when the pointer is not useful (we are not talking about the garbage collector here). What if the pointer itself takes care of that? Yes, that’s exactly what smart pointers are intended to do. Let us write a smart pointer
and see how we can handle a pointer better.
We shall start with a realistic example. Let’s say we have a class called
Hide Copy Code
Now we shall write the client code to use
Hide Copy Code
Now look at this code, every time I create a pointer, I need to take care of deleting it. This is exactly what I want to avoid. I need some automatic mechanism which deletes the pointer. One thing which strikes to me is a destructor. But pointers do not
have destructors, so what? Our smart pointer can have one. So we will create a class called
to the
something like this:
Hide Copy Code
Note the following things:
We have created an object of class
pointer. Since the destructor of the
pointer (as its main responsibility); hence we don’t have the pain of deleting the pointer.
One more thing of major importance is that we should be able to call the
using the
pointer, i.e., the class should behave exactly like a pointer.
Since the smart pointer should behave like a pointer, it should support the same interface as pointers do; i.e., they should support the following operations.
Dereferencing (operator
Indirection (operator
Let us write the
Hide Copy Code
This class is our smart pointer class. The main responsibility of this class is to hold a pointer to the
and then delete it when its destructor is called. It should also support the interface of the pointer.
One problem which we see here is that we can use this smart pointer class for a pointer of the
This means that we have to create a smart pointer class for each type, and that’s not easy. We can solve this problem by making use of templates and making this smart pointer class generic. So let us change the code like this:
Hide Shrink
Copy
Code
Now we can use our smart pointer class for any type of pointer. So is our smart pointer really smart? Check the following code segment.
Hide Copy Code
Look what happens here.
referring to the same
out of scope, the destructor of
pointer. Now we cannot call
be left with a dangling pointer and this call will fail. (Note that this problem would have existed even if we were using normal pointers instead of smart pointers.) We should not delete the
pointer unless no body is using it. How do we do that? Implementing a reference counting mechanism in our smart pointer class will solve this problem.
What we are going to do is we will have a reference counting class
which represents the reference count. We will have methods to increment and decrement the reference count.
Hide Copy Code
Now that we have a reference counting class, we will introduce this to our smart pointer class. We will maintain a pointer to class
our
For this to happen, we need to have an assignment operator and copy constructor in our
Hide Shrink
Copy
Code
Let us have a look at the client code.
Hide Copy Code
When we create a smart pointer
the constructor of
will be created. The
called to increment the reference count to 1. Now
the copy constructor. Here the data will be copied and the reference will again be incremented to 2. Now
call the assignment operator to assign the value of
Here also we copy the data and increment the reference count, thus making the count 3. When
out of scope, the destructors of the respective objects will be called. Here the reference count will be decremented, but data will not be deleted unless the reference count becomes zero. This happens only when the destructor of
called. Hence our data will be deleted only when no body is referring to it.
Memory leaks: Using smart pointers reduces the work of managing pointers for memory leaks. Now you could create a pointer and forget about deleting it, the smart pointer will do that for you. This is the simplest garbage collector we could think of.
Exceptions: Smart pointers are very useful where exceptions are used. For example, look at the following code:
Hide Copy Code
We are using a normal pointer here and deleting it after using, so every thing looks okay here. But what if our
throws some exception?
Hide Copy Code
Don't you think this is an overhead of catching an exception and re-throwing it? This code becomes cumbersome if you have many pointers created. How will a smart pointer help here? Let's have a look at the same code if a smart pointer is used.
Hide Copy Code
We are making use of a smart pointer here; yes, we don’t need to catch the exception here. If the
an exception, stack unwinding will happen for the function and during this, the destructor of all local objects will be called, hence the destructor of
be called which will release the memory, hence we are safe. So this makes it very useful to use smart pointers here.
Smart pointers are useful for writing safe and efficient code in C++. Make use of smart pointers and take the advantage of garbage collection. Take a look at Scott Meyers' auto_ptr implementation in STL.
Introduction
What are smart pointers? The answer is fairly simple; a smart pointer is a pointer which is smart. What does that mean? Actually, smart pointers are objects which behave like pointers but do more than a pointer. These objects are flexible as pointers andhave the advantage of being an object (like constructor and destructors called automatically). A smart pointer is designed to handle the problems caused by using normal pointers (hence called smart).
Problems with pointers
What are the common problems we face in C++ programs while using pointers? The answer is memory management. Have a look at the following code:Hide Copy Code
char* pName = new char[1024]; … SetName(pName); … … if(null != pName) { delete[] pName; }
How many times have we found a bug which was caused because we forgot to delete
pName. It would be great if someone
could take care of releasing the memory when the pointer is not useful (we are not talking about the garbage collector here). What if the pointer itself takes care of that? Yes, that’s exactly what smart pointers are intended to do. Let us write a smart pointer
and see how we can handle a pointer better.
We shall start with a realistic example. Let’s say we have a class called
Personwhich is defined as below.
Hide Copy Code
class Person { int age; char* pName; public: Person(): pName(0),age(0) { } Person(char* pName, int age): pName(pName), age(age) { } ~Person() { } void Display() { printf("Name = %s Age = %d \n", pName, age); } void Shout() { printf("Ooooooooooooooooo",); } };
Now we shall write the client code to use
Person.
Hide Copy Code
void main() { Person* pPerson = new Person("Scott", 25); pPerson->Display(); delete pPerson; }
Now look at this code, every time I create a pointer, I need to take care of deleting it. This is exactly what I want to avoid. I need some automatic mechanism which deletes the pointer. One thing which strikes to me is a destructor. But pointers do not
have destructors, so what? Our smart pointer can have one. So we will create a class called
SPwhich can hold a pointer
to the
Personclass and will delete the pointer when its destructor is called. Hence my client code will change to
something like this:
Hide Copy Code
void main() { SP p(new Person("Scott", 25)); p->Display(); // Dont need to delete Person pointer.. }
Note the following things:
We have created an object of class
SPwhich holds our
Personclass
pointer. Since the destructor of the
SPclass will be called when this object goes out of scope, it will delete the
Personclass
pointer (as its main responsibility); hence we don’t have the pain of deleting the pointer.
One more thing of major importance is that we should be able to call the
Displaymethod
using the
SPclass object the way we used to call using the
Personclass
pointer, i.e., the class should behave exactly like a pointer.
Interface for a smart pointer
Since the smart pointer should behave like a pointer, it should support the same interface as pointers do; i.e., they should support the following operations.Dereferencing (operator
*)
Indirection (operator
->)
Let us write the
SPclass now.
Hide Copy Code
class SP { private: Person* pData; // pointer to person class public: SP(Person* pValue) : pData(pValue) { } ~SP() { // pointer no longer requried delete pData; } Person& operator* () { return *pData; } Person* operator-> () { return pData; } };
This class is our smart pointer class. The main responsibility of this class is to hold a pointer to the
Personclass
and then delete it when its destructor is called. It should also support the interface of the pointer.
Generic smart pointer class
One problem which we see here is that we can use this smart pointer class for a pointer of the Personclass only.
This means that we have to create a smart pointer class for each type, and that’s not easy. We can solve this problem by making use of templates and making this smart pointer class generic. So let us change the code like this:
Hide Shrink
Copy
Code
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; } }; void main() { SP<PERSON> p(new Person("Scott", 25)); p->Display(); // Dont need to delete Person pointer.. }
Now we can use our smart pointer class for any type of pointer. So is our smart pointer really smart? Check the following code segment.
Hide Copy Code
void main() { SP<PERSON> p(new Person("Scott", 25)); p->Display(); { SP<PERSON> q = p; q->Display(); // Destructor of Q will be called here.. } p->Display(); }
Look what happens here.
pand
qare
referring to the same
Personclass pointer. Now when
qgoes
out of scope, the destructor of
qwill be called which deletes the
Personclass
pointer. Now we cannot call
p->Display();since
pwill
be left with a dangling pointer and this call will fail. (Note that this problem would have existed even if we were using normal pointers instead of smart pointers.) We should not delete the
Personclass
pointer unless no body is using it. How do we do that? Implementing a reference counting mechanism in our smart pointer class will solve this problem.
Reference counting
What we are going to do is we will have a reference counting class RC. This class will maintain an integer value
which represents the reference count. We will have methods to increment and decrement the reference count.
Hide Copy Code
class RC { private: int count; // Reference count public: void AddRef() { // Increment the reference count count++; } int Release() { // Decrement the reference count and // return the reference count. return --count; } };
Now that we have a reference counting class, we will introduce this to our smart pointer class. We will maintain a pointer to class
RCin
our
SPclass and this pointer will be shared for all instances of the smart pointer which refers to the same pointer.
For this to happen, we need to have an assignment operator and copy constructor in our
SPclass.
Hide Shrink
Copy
Code
template < typename T > class SP { private: T* pData; // pointer RC* reference; // Reference count public: SP() : pData(0), reference(0) { // Create a new reference reference = new RC(); // Increment the reference count reference->AddRef(); } SP(T* pValue) : pData(pValue), reference(0) { // Create a new reference reference = new RC(); // Increment the reference count reference->AddRef(); } SP(const SP<T>& sp) : pData(sp.pData), reference(sp.reference) { // Copy constructor // Copy the data and reference pointer // and increment the reference count reference->AddRef(); } ~SP() { // Destructor // Decrement the reference count // if reference become zero delete the data if(reference->Release() == 0) { delete pData; delete reference; } } T& operator* () { return *pData; } T* operator-> () { return pData; } SP<T>& operator = (const SP<T>& sp) { // Assignment operator if (this != &sp) // Avoid self assignment { // Decrement the old reference count // if reference become zero delete the old data if(reference->Release() == 0) { delete pData; delete reference; } // Copy the data and reference pointer // and increment the reference count pData = sp.pData; reference = sp.reference; reference->AddRef(); } return *this; } };
Let us have a look at the client code.
Hide Copy Code
void main() { SP<PERSON> p(new Person("Scott", 25)); p->Display(); { SP<PERSON> q = p; q->Display(); // Destructor of q will be called here.. SP<PERSON> r; r = p; r->Display(); // Destructor of r will be called here.. } p->Display(); // Destructor of p will be called here // and person pointer will be deleted }
When we create a smart pointer
pof type
Person,
the constructor of
SPwill be called, the data will be stored, and a new
RCpointer
will be created. The
AddRefmethod of
RCis
called to increment the reference count to 1. Now
SP q = p;will create a new smart pointer
qusing
the copy constructor. Here the data will be copied and the reference will again be incremented to 2. Now
r = p;will
call the assignment operator to assign the value of
pto
q.
Here also we copy the data and increment the reference count, thus making the count 3. When
rand
qgo
out of scope, the destructors of the respective objects will be called. Here the reference count will be decremented, but data will not be deleted unless the reference count becomes zero. This happens only when the destructor of
pis
called. Hence our data will be deleted only when no body is referring to it.
Applications
Memory leaks: Using smart pointers reduces the work of managing pointers for memory leaks. Now you could create a pointer and forget about deleting it, the smart pointer will do that for you. This is the simplest garbage collector we could think of.Exceptions: Smart pointers are very useful where exceptions are used. For example, look at the following code:
Hide Copy Code
void MakeNoise() { Person* p = new Person("Scott", 25); p->Shout(); delete p; }
We are using a normal pointer here and deleting it after using, so every thing looks okay here. But what if our
Shoutfunction
throws some exception?
delete p;will never be called. So we have a memory leak. Let us handle that.
Hide Copy Code
void MakeNoise() { Person* p = new Person("Scott", 25); try { p->Shout(); } catch(...) { delete p; throw; } delete p; }
Don't you think this is an overhead of catching an exception and re-throwing it? This code becomes cumbersome if you have many pointers created. How will a smart pointer help here? Let's have a look at the same code if a smart pointer is used.
Hide Copy Code
void MakeNoise() { SP<Person> p(new Person("Scott", 25)); p->Shout(); }
We are making use of a smart pointer here; yes, we don’t need to catch the exception here. If the
Shoutmethod throws
an exception, stack unwinding will happen for the function and during this, the destructor of all local objects will be called, hence the destructor of
pwill
be called which will release the memory, hence we are safe. So this makes it very useful to use smart pointers here.
Conclusion
Smart pointers are useful for writing safe and efficient code in C++. Make use of smart pointers and take the advantage of garbage collection. Take a look at Scott Meyers' auto_ptr implementation in STL.
相关文章推荐
- c++primer plus第十七章-输入和输出概述
- 黑马程序员——OC语言基础---description方法介绍及重写
- 黑马程序员——OC语言基础---extern和static关键字
- C++初始化变量
- 黑马程序员——OC语言基础---Static关键字使用
- OC语言-NSMutableArray为什么要用strong来修饰
- 黑马程序员——OC语言基础---封装的概念及原理
- c语言'\0' 意思
- C++中的函数模板&&类模板
- C++输入输出流实现未知数目的输入(相当于实现python的split函数)
- C++中的try..catch
- C++源代码生成可执行文件的四个步骤
- C语言类与对象的实现
- 学习C语言感悟
- C语言设计模式与接口
- C语言中的高级指针
- C++实现单链表
- C++各种崩溃分析
- C语言文件输出,把内容输入到新建文件中
- C++引用&概念及用法