Analysis of "Smart Pointer in C++" (1): shared_ptr pointer (detailed explanation)

Analysis of "Smart Pointer in C++"

The characteristics of the four smart pointers

① unique_ptr pointer: for a pointer to the memory opened in the heap area, the pointer to this memory area in the entire pointer scope can only be himself, whoever wants to grab control with it, the compiler will directly report an error;

② shared_ptr pointer: For the memory opened in the heap area, I can use multiple pointers to point to it, which is equivalent to first opening up a piece of memory in the heap area and using a pointer to point to this memory area, and then giving this pointer many aliases;

③ auto_ptr pointer: the degree of intelligence of this pointer is relatively low, auto_ptr can only manage a pointer to the heap memory and ensure that there is and only one pointer to this area, otherwise it will cause an abnormal error like "shallow copy" repeated release;

④ Weak_ptr pointer: Used to solve the problem of using "circular references", use weak_ptr instead of shared_ptr. Unlike shared_ptr, weak_ptr is a weak shared pointer. When a shared_ptr pointer is assigned to a weak_ptr pointer, the reference count will not change.

note:

① The memory areas maintained by these pointers are all opened up in the heap area, and the bottom layer is implemented using new expression and delete expression;

② When using these pointers to maintain the pointer returned by new[], the operation is slightly different from that of the pointer returned by new:

#include <iostream>  
using namespace std;  
#include <memory>  
  
int main()  
{  
    shared_ptr<int[]> ptr1(new int[2]), ptr2;  
    // shared_ptr<int*> ptr3;
    ptr1[0] = 10;  
    ptr1[1] = 20;  
    ptr2 = ptr1;  
    cout << ptr2[1] << endl;  
    //cout << *(ptr2) << endl; // 错误  
    //cout << *(ptr2 + 1) << endl; // 错误  
}  

 

note:

⑴ First of all, these smart pointers are not real pointers, but template classes. Since there is no "+int integer" operation like ordinary pointers in the template class, the address offset cannot be realized by "+int integer";

⑵ Secondly, we should note: when constructing a smart template class pointer object that points to "new[] return pointer", the template parameter must be "array-like data type pointer", such as double[],int[]…… Wait, but if we use "pointers of data types that do not have array properties", we will get an error:

 

⑶ In response to the precautions mentioned in "C++ Primer", "In addition to the unique_ptr pointer that can maintain new[] and the pointer returned by new, the other three smart pointers weak_ptr, shared_ptr, and auto_ptr can only maintain the pointer returned by new", after I tried It was found that these precautions did not exist.

③ One note: Smart template pointer objects of the same attribute instantiated by different template parameters cannot be assigned to each other:

 

Reference count pointer shared_ptr

① The establishment and initialization of shared_ptr

#include <iostream>  
using namespace std;  
#include <memory>  
  
int main()  
{  
    shared_ptr<int[]> ptr1(new int[2]{ 1,2 }); // 使用new[]返回值初始化  
    shared_ptr<int> ptr2(new int(10)); // 使用new返回值初始化  
    shared_ptr<int> ptr3(ptr2); // 使用相同类型的模板对象进行初始化  
    int* ptr4 = new int(1);  
    shared_ptr<int> ptr5(ptr4); // 使用建立在堆区的指针去初始化模板指针对象  
  
    shared_ptr<int> ptr8 = make_shared<int>(19); // 使用make_shared在堆区开辟的内存空间  
    allocator<int> alloc1;  
    shared_ptr<int> ptr9 = allocate_shared<int>(alloc1, 10); // 使用迭代器在堆区开辟的内存空间  
}  

 

It seems that there are many types, but they can be divided into two categories:

 

We often use two functions to construct shared_ptr objects:

⑴ make_shared Function:

函数原型:shared_ptr<type_name> make_shared<type_name>(value)

Code example:

// make_shared example  
#include <iostream>  
#include <memory>  
  
int main () {  
  
  std::shared_ptr<int> foo = std::make_shared<int> (10);  
  // same as:  
  std::shared_ptr<int> foo2 (new int(10));  
  
  shared_ptr<int> bar = std::make_shared<int> (20);  
  
  shared_ptr< std::pair<int,int>> baz = std::make_shared<std::pair<int,int>> (30,40);  
  
  std::cout << "*foo: " << *foo << '\n';  
  std::cout << "*bar: " << *bar << '\n';  
  std::cout << "*baz: " << baz->first << ' ' << baz->second << '\n';  
  
  return 0;  
}  

 

operation result:

In addition, the use of make_shared for initialization also has its own unique role: auxiliary functions for smart pointers

⑵ allocate_shared function:

函数原型: shared_ptr<type_name> allocate_shared<type_name>(std::allocate<type_name> obj,value)

Code example:

// allocate_shared example  
#include <iostream>  
#include <memory>  
  
int main () {  
  std::allocator<int> alloc;    // the default allocator for int  
  std::default_delete<int> del; // the default deleter for int  
  
  std::shared_ptr<int> foo = std::allocate_shared<int> (alloc,10);  
  
  auto bar = std::allocate_shared<int> (alloc,20);  
  
  auto baz = std::allocate_shared<std::pair<int,int>> (alloc,30,40);  
  
  std::cout << "*foo: " << *foo << '\n';  
  std::cout << "*bar: " << *bar << '\n';  
  std::cout << "*baz: " << baz->first << ' ' << baz->second << '\n';  
  
  return 0;  
}  

 

operation result:

 

⑶ The difference between make_shared function and allocate_shared function:

We see the make_shared definition source code is as follows:

// FUNCTION TEMPLATE make_shared  
emplate<class _Ty,  
class... _Types>  
_NODISCARD inline shared_ptr<_Ty> make_shared(_Types&&... _Args)  
{   // make a shared_ptr  
const auto _Rx = new _Ref_count_obj<_Ty>(_STD forward<_Types>(_Args)...);  
  
shared_ptr<_Ty> _Ret;  
_Ret._Set_ptr_rep_and_enable_shared(_Rx->_Getptr(), _Rx);  
return (_Ret);  
}  

 

The make_shared function uses new to directly open up a piece of memory in the heap area, while the allocate_shared function can be seen from the definition that it uses the allocator memory allocator for maintenance. The way to open up memory is the essential difference between the two functions, just like the "cplusplus.com" website As shown:

make_shared function uses ::new to allocate storage for the object. A similar function, allocate_shared, accepts an allocator as argument and uses it to allocate the storage.

The translation is as follows:

The make_shared function uses ::new to allocate storage space for the object. A similar function allocate_shared accepts the allocator as a parameter and uses it to allocate storage space.

Access the object pointed to by the pointer

#include <iostream>  
using namespace std;  
#include <memory>  
  
int main()  
{  
    shared_ptr<int[]> ptr(new int[2]{ 1,2 });  
    cout << ptr[0] << endl; // 用[]元素下标来访问  
    cout << ptr[1] << endl;  
    shared_ptr<pair<int, int>[]> ptr1(new pair<int, int>[2]);  
    ptr1[0] = pair<int, int>(3, 4);  
    ptr1[1] = pair<int, int>(5, 6);  
    cout << ptr1[0].first << " " << ptr1[0].second << endl;  
    cout << ptr1[1].first << " " << ptr1[1].second << endl;  
    // ptr1->first; // 这种访问pair元素的方式不对,应该使用ptr1[0].first这种方式去访问pair中的元素  
}  

 

Usage of member functions in shared_ptr smart pointer template class

① get function: return the storage address of the smart pointer

⑴ Function function:

Returns the address pointed to by the shared_ptr pointer

⑵ Code example:

#include <iostream>  
using namespace std;  
#include <memory>  
  
int main()  
{  
    shared_ptr<int> ptr(new int(10));  
    cout << *ptr.get() << endl; // 返回地址解引用后的值  
}  

 

② Owner_before member function:

Please refer to " Detailed explanation of owner_before member function ".

③ reset reset member function:

⑴ The role of the reset member function:

The meaning of reset in Chinese is "reset", and its function in shared_ptr is to make the pointer point to a new memory area.

⑵ Code example:

#include<iostream>  
using namespace std;  
#include <memory>  
  
int main()  
{  
    shared_ptr<int> ptr = make_shared<int>(10), ptr1(ptr);  
    cout << "指向10所在地址的指针个数为" << ptr.use_count() << endl;  
    cout << "*ptr1 = " << *ptr1 << endl;  
    cout << "*ptr = " << *ptr << endl;  
    ptr.reset(new int(90));  
    cout << "指向90所在地址的指针个数为" << ptr.use_count() << endl;  
    cout << "*ptr1 = " << *ptr1 << endl;  
    cout << "*ptr = " << *ptr << endl;  
}  

 

operation result:

 

We see that when ptr.reset(new int(90)) is called, ptr no longer points to the memory area where 10 is located, but instead points to the memory area where 90 is located, which means that the reset member function can change the pointer to Area.

Before reset is executed:

 

After reset is executed:

⑶ Extended reset function:

void reset() noexcept;  
template <class U> void reset (U* p);   
template <class U, class D> void reset (U* p, D del);  
template <class U, class D, class Alloc> void reset (U* p, D del, Alloc alloc);  

 

The above are the three forms of unique_ptr.

The parameter p is the same pointer as the unique_ptr template parameter, del can be the deleter defined by the system or our custom deleter object, and alloc must be the allocator template class object provided by the system.

#include <iostream>  
#include <memory>  
using namespace std;  
  
template <typename T>  
class deleter  
{  
private:  
    int count;  
public:  
    deleter() :count(0) {};  
    void ShowInf()  
    {  
        cout << "调用此删除器次数为" << this->count << endl;  
    }  
    void operator()(T* ptr)  
    {  
        this->count++;  
        cout << "到目前为止,调用删除器次数为" << this->count << endl;  
        delete ptr;  
    }  
};  
  
int main()  
{  
    shared_ptr<int> ptr(new int(10));  
    ptr.reset(new int(11), deleter<int>(), allocator<int>()); // 我这里使用的都是临时变量  
}  

 

operation result:

④ The member function of swap exchange elements

⑴ Function:

Exchange the addresses of the two memory spaces, that is, exchange the addresses pointed to by the two pointers.

⑵ Code example:

#include <iostream>  
using namespace std;  
#include <memory>  
  
int main()  
{  
    shared_ptr<int> ptr = make_shared<int>(10), ptr1(ptr);  
    shared_ptr<int> ptr2 = make_shared<int>(90);  
    cout << "ptr指向的地址:" << ptr.get() << endl;  
    cout << "ptr2指向的地址:" << ptr2.get() << endl;  
    cout << "指向10所在内存区域的指针个数:" << ptr.use_count() << endl;  
    ptr.swap(ptr2);  
    cout << "ptr指向的地址:" << ptr.get() << endl;  
    cout << "ptr2指向的地址:" << ptr2.get() << endl;  
    cout << "指向90所在内存区域的指针个数:" << ptr.use_count() << endl;  
}  

 

operation result:

 

The essence of the swap member function is as follows:

 

The addresses pointed to by the two pointers are swapped

From the comprehensive point of view of the code and output results, the attributes of ptr and ptr2 are completely exchanged.

⑤ unique member function: determine whether the pointer uniquely points to a memory area

Code example:

#include <iostream>  
using namespace std;  
#include <memory>  
  
int main()  
{  
    shared_ptr<int> ptr = make_shared<int>(10), ptr1(ptr);  
    cout << "ptr是否是指向10所在内存区域的唯一指针:" << ptr.unique() << endl;  
}  

 

operation result:

 

⑥ use_count member function: Point out that there are several pointers to the same memory area

Code example:

#include <iostream>  
using namespace std;  
#include <memory>  
  
int main()  
{  
    shared_ptr<int> ptr = make_shared<int>(10), ptr1(ptr);  
    cout << "ptr是否是指向10所在内存区域的唯一指针:" << ptr.unique() << endl;  
    cout << "指向10所在内存区域的指针的数量:" << ptr.use_count() << endl;  
}  

 

operation result:

 

How does the shared_ptr template class maintain the data members in the class type?

#include <iostream>  
using namespace std;  
#include <memory>  
  
struct A   
{  
    int age;  
    double mark;  
};  
  
int main()  
{  
    shared_ptr<A> ptr = make_shared<A>();  
    // 参数一:堆区对象所在的地址;  
    // 参数二:对象中数据成员的地址  
    shared_ptr<int> ptr1(ptr, &ptr->age);  
    // ptr1为指向*ptr对象中的age数据成员  
}  

 

Some people say that we don’t need to write the first parameter ptr, can’t we?

If you run the following code, an error will occur, the code is as follows:

#include <iostream>  
using namespace std;  
#include <memory>  
  
struct A   
{  
    int age;  
    double mark;  
};  
  
int main()  
{  
    shared_ptr<A> ptr = make_shared<A>();  
    shared_ptr<int> ptr1(&ptr->age); // 去掉了原来的第一个参数ptr  
}  

 

The error is as follows:

 

Why did it report an error?

Because only the entire class can have a destructor, when the life of the smart pointer to a data member ends, the destructor of the class will be called to delete the data and release the memory. However, if the data type of the data member is a basic data type, the basic The data type has no destructor, so the compiler will report an error.

Guess you like

Origin blog.csdn.net/weixin_45590473/article/details/113049522