c++ series – smart pointers

1. The use and principle of smart pointers

1.1 RAII

RAII (Resource Acquisition Is Initialization) is a simple technology that uses the object life cycle to control program resources (such as memory, file handles, network connections, mutexes, etc.).

Acquire resources when the object is constructed, then control access to the resources so that they remain valid throughout the object’s life cycle, and finally release the resources when the object is destroyed. Through this, we actually delegate the responsibility of managing a resource to an object.

This approach has two major benefits:

1. There is no need to explicitly release resources.

2. In this way, the resources required by the object remain valid throughout its lifetime.

//SmartPtr class designed using RAII ideas
template<class T>
class SmartPtr {
public:
    SmartPtr(T* ptr = nullptr)
       : _ptr(ptr)
   {}
    ~SmartPtr()
   {
        if(_ptr)
            delete _ptr;
   }
    
private:
    T* _ptr;
};
int div()
{
 int a, b;
 cin >> a >> b;
 if(b==0)
 throw invalid_argument("divide by 0 error");
 return a / b;
}
voidFunc()
{
 ShardPtr<int> sp1(new int);
    ShardPtr<int> sp2(new int);
 cout << div() << endl;
}
int main()
{
    try {
 Func();
   }
    catch(const exception & e)
   {
        cout<<e.what()<<endl;
   }
 return 0;
}

1.2 Principle of smart pointers

The above-mentioned SmartPtr cannot be called a smart pointer because it does not yet have the behavior of a pointer. The pointer can be dereferenced, or the content in the pointed space can be accessed through ->. Therefore: * and -> must be overloaded in theAutoPtr template class before it can be used like a pointer.

template<class T>
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr)
     : _ptr(ptr)
 {}
~SmartPtr()
 {
     if(_ptr)
         delete _ptr;
 }
T & amp; operator*() {return *_ptr;}
T* operator->() {return _ptr;}
private:
T* _ptr;
};
struct Date
{
    int _year;
 int _month;
 int _day;
};
int main()
{
SmartPtr<int> sp1(new int);
*sp1 = 10
cout<<*sp1<<endl;
SmartPtr<int> sparray(new Date);
// It should be noted that this should be sparkray.operator->()->_year = 2018;
// It should have been sparkray->->_year. For the sake of readability, one -> is omitted grammatically.
sparray->_year = 2018;
sparray->_month = 1;
sparray->_day = 1;
}

To summarize the principles of smart pointers:

1. RAII features

2. Overload operator* and operator->, with pointer-like behavior

1.3 std::auto_ptr

The C++98 version of the library provides the auto_ptr smart pointer. The use and problems of auto_ptr demonstrated below. The implementation principle of auto_ptr: the idea of management rights transfer. The following simplifies the simulation and implements a copy of bit::auto_ptr to understand its principle

// C++98 management rights transfer auto_ptr
namespace bit
{
 template<class T>
 class auto_ptr
 {
 public:
 auto_ptr(T* ptr)
 :_ptr(ptr)
 {}
 auto_ptr(auto_ptr<T> & amp; sp)
 :_ptr(sp._ptr)
 {
 //Transfer of management rights
 sp._ptr = nullptr;
 }
 auto_ptr<T> & amp; operator=(auto_ptr<T> & amp; ap)
 {
 // Check whether to assign a value to yourself
 if (this != & amp;ap)
 {
 // Release resources in the current object
 if(_ptr)
 delete _ptr;
 //Transfer resources in ap to the current object
 _ptr = ap._ptr;
 ap._ptr = NULL;
 }
 return *this;
 }
 ~auto_ptr()
 {
 if(_ptr)
 {
 cout << "delete:" << _ptr << endl;
 delete _ptr;
}
 }
 // Use like pointer
 T & operator*()
 {
 return *_ptr;
 }
 T* operator->()
 {
 return _ptr;
 }
 private:
 T* _ptr;
 };
}
// Conclusion: auto_ptr is a failed design. Many companies explicitly require that auto_ptr cannot be used.
//int main()
//{
// std::auto_ptr<int> sp1(new int);
// std::auto_ptr<int> sp2(sp1); // Management rights transfer
//
// // sp1 is left floating
// *sp2 = 10;
// cout << *sp2 << endl;
// cout << *sp1 << endl;
// return 0;
//}

1.4 std::unique_ptr

A more reliable unique_ptr is available in C++11

The implementation principle of unique_ptr: Simple and crude copy protection. The following simplifies the simulation and implements a copy of UniquePtr to understand its principle.

//C++11 library only updated smart pointer implementation
// Before C++11 came out, boost eliminated the more useful scoped_ptr/shared_ptr/weak_ptr
// C++11 absorbs the essence of smart pointers in the boost library
// C++11->unique_ptr/shared_ptr/weak_ptr
// unique_ptr/scoped_ptr
// Principle: Simple and crude -- anti-copy
namespace bit
{
 template<class T>
 class unique_ptr
 {
 public:
 unique_ptr(T* ptr)
 :_ptr(ptr)
 {}
 ~unique_ptr()
{
 if(_ptr)
 {
 cout << "delete:" << _ptr << endl;
 delete _ptr;
 }
 }
 // Use like pointer
 T & operator*()
 {
 return *_ptr;
 }
 T* operator->()
 {
 return _ptr;
 }
 unique_ptr(const unique_ptr<T> & amp; sp) = delete;
 unique_ptr<T> & amp; operator=(const unique_ptr<T> & amp; sp) = delete;
 private:
 T* _ptr;
 };
}
//int main()
//{
// /*bit::unique_ptr<int> sp1(new int);
// bit::unique_ptr<int> sp2(sp1);*/
//
// std::unique_ptr<int> sp1(new int);
// //std::unique_ptr<int> sp2(sp1);
//
// return 0;
//}

1.5 std::shared_ptr

C++11 begins to provide a more reliable shared_ptr that supports copying

The principle of shared_ptr: sharing resources between multiple shared_ptr objects is achieved through reference counting.

1. Shared_ptr internally maintains a count for each resource to record that the resource is shared by several objects.

2. When the object is destroyed (that is, the destructor is called), it means that you are no longer using the resource, and the reference count of the object is reduced by one.

3. If the reference count is 0, it means that you are the last object to use the resource, and the resource must be released

4. If it is not 0, it means that other objects besides itself are using the resource, and the resource cannot be released, otherwise other objects will become wild pointers.

//Reference counting supports multiple copies to manage the same resource, and the last destructed object releases the resource.
namespace bit
{
 template<class T>
 class shared_ptr
 {
 public:
 shared_ptr(T* ptr = nullptr)
 :_ptr(ptr)
 , _pRefCount(new int(1))
 , _pmtx(new mutex)
 {}
 shared_ptr(const shared_ptr<T> & amp; sp)
 :_ptr(sp._ptr)
 , _pRefCount(sp._pRefCount)
 , _pmtx(sp._pmtx)
 {
 AddRef();
 }
 void Release()
 {
 _pmtx->lock();
 bool flag = false;
 if (--(*_pRefCount) == 0 & amp; & amp; _ptr)
 {
 cout << "delete:" << _ptr << endl;
 delete _ptr;
 delete _pRefCount;
 flag = true;
 }
 _pmtx->unlock();
 if (flag == true)
 {
 delete _pmtx;
 }
 }
 void AddRef()
 {
 _pmtx->lock();
  + + (*_pRefCount);
 _pmtx->unlock();
 }
 shared_ptr<T> & operator=(const shared_ptr<T> & sp)
 {
 //if (this != & amp;sp)
 if (_ptr != sp._ptr)
 {
 Release();
_ptr = sp._ptr;
 _pRefCount = sp._pRefCount;
 _pmtx = sp._pmtx;
 AddRef();
 }
 return *this;
 }
 int use_count()
 {
 return *_pRefCount;
 }
 ~shared_ptr()
 {
 Release();
 }
 // Use like pointer
 T & operator*()
 {
 return *_ptr;
 }
 T* operator->()
 {
 return _ptr;
 }
 T* get() const
 {
 return _ptr;
 }
 private:
 T* _ptr;
 int* _pRefCount;
 mutex* _pmtx;
 };
    
    // Simplified version of weak_ptr implementation
 template<class T>
 class weak_ptr
 {
 public:
 weak_ptr()
 :_ptr(nullptr)
 {}
 weak_ptr(const shared_ptr<T> & amp; sp)
 :_ptr(sp.get())
 {}
 weak_ptr<T> & amp; operator=(const shared_ptr<T> & amp; sp)
 {
 _ptr = sp.get();
 return *this;
}
 T & operator*()
 {
 return *_ptr;
 }
 T* operator->()
 {
 return _ptr;
 }
 private:
 T* _ptr;
 };
}

Are shared_ptr smart pointers thread-safe?

Yes, the addition and subtraction of reference counts are protected by locking. But pointing to resources is not thread-safe

Thread safety issues pointing to resources on the heap are handled by the person accessing them. Smart pointers do not care about them and cannot control them.

The thread safety issue of reference counting is what smart pointers have to deal with

Thread safety issues of std::shared_ptr We use the following program to test the thread safety issues of shared_ptr. It should be noted that the thread safety of shared_ptr is divided into two aspects:

1. The reference count in a smart pointer object is shared by multiple smart pointer objects. The reference count of the smart pointer in two threads is + + or — at the same time. This operation is not atomic. The reference count was originally 1, + + twice, it may still be 2. In this way, the reference count will be messed up. This can cause problems such as resources not being released or the program crashing. Therefore, only the reference counting + + and — in the pointer need to be locked, which means that the reference counting operation is thread-safe.

2. Objects managed by smart pointers are stored on the heap and accessed by two threads at the same time will lead to thread safety issues.

2.The relationship between C++11 and smart pointers in boost

Relationship between smart pointers in C++11 and boost

1. The first smart pointer auto_ptr was produced in C++98.

2. C++ boost gives more practical scoped_ptr, shared_ptr and weak_ptr.

3. C++TR1, introduced shared_ptr, etc. But please note that TR1 is not the standard version.

4. C++11, unique_ptr, shared_ptr and weak_ptr were introduced. It should be noted that unique_ptr corresponds to boost’s scoped_ptr. And the implementation principles of these smart pointers refer to the implementation in boost.

The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. Algorithm skill tree Home page Overview 57141 people are learning the system