Unmanned CPP shared pointer

Unmanned CPP shared pointer

Usage of shared pointers

The shared_ptr is provided in the current Boost library and should be a template class provided in the standard library of C++1x in the future. Previously, the "auto_ptr" in the ISO/IEC 14882:2003 standard library had similar functions. Obviously shared_ PTR is better than auto_ PTR should be more powerful in terms of function. This article mainly introduces shared_ Usage and precautions of PTR.

1. shared_ Function of PTR

shared_ The main function of PTR is to manage the destruction of dynamically created objects. Its basic principle is to record the number of times an object is referenced. When the number of references is 0, that is, when the last shared pointer pointing to an object is destructed, the destructor of the shared pointer releases the memory area pointed to.

Compared with ordinary pointers, shared pointer objects overload the *, - > and = = operators, so you can use it like normal pointers. There are no overloaded +, -, + +, –, [] and other algorithms.

2. shared_ Principle of PTR

It follows the concept of shared ownership, that is, different shared_ptr objects can be associated with the same pointer, and this is achieved internally using a reference counting mechanism.

(1) Each shared_ The PTR object internally points to two memory locations:
1. Pointer to the object.
2. Pointer used to control reference count data. (the reference count here is the number of shared pointers pointing to the memory block)

(2) How shared ownership works with the help of reference counts:
1. When the new shared_ When a PTR object is associated with an original shared pointer, the reference count associated with this pointer is increased by 1 in its constructor.
2. When any related shared_ When a PTR object is destructed, for example, when a local function call ends, it subtracts the reference count of the associated pointer by 1 in its destructor. If the reference count becomes 0, there are no other shared_ The PTR object is associated with this memory, in which case it uses the delete function to delete the corresponding memory in the heap.

3. shared_ptr Library

3.1. For Visual C++ 2010

At present, the library of Visual C++ 2010 already contains shared_ptr template class, that is, you can write it directly as follows:

  #include <memory>

3.2. For other compilers that support the ISO/IEC 14882:2003 standard

The GNU G + + standard library does not support it (after all, it is a future standard). If you want to use shared in G + +_ PTR still needs to use the Boost library, that is, in G + +, you have to write:

 #include <boost/shared_ptr.hpp>

4. shared_ Construction of PTR object

To be safe, you should only construct a shared pointer in the following ways (in the following example, T represents the type of object pointed to by the shared pointer unless otherwise specified):

4.1. Construct with null argument constructor

In other words, you can directly define a shared_ptr without specifying the contents of the constructor:

 std::shared_ptr<T> ptr;

In this way, ptr is equivalent to a NULL pointer. When you try to do something like * ptr or ptr - > XX on a NULL pointer, you should receive an exception.

4.2. Constructed directly from the return value of the new operator

std::shared_ptr<T> ptr(new T());

The above line of code creates two pieces of memory in the heap: 1 Store T. 2. Memory used for reference counting, and manage the shared memory attached to this memory_ The count of PTR objects. The initial count will be 1

Because shared with parameters_ The copy constructor (4.3) of PTR is of explicit type, so STD:: shared cannot be used like this_ ptr ptr = new T(); Implicitly call its constructor.

But the link says to construct a new shared_ The best way to PTR objects is to make std::make_shared class template. Because 1) it allocates memory for the T object and the data used for reference counting at one time, while the new operator only allocates memory for T (I don'T understand, ask an expert for an answer). 2) It can avoid some errors caused by heap pointer or new allocation pointer.

 std::shared_ptr<T> p1=std::make_shared<T> ();

4.3. Use the copy constructor (or equal sign overload) from other shared_ Object construction of PTR

std::shared_ptr<T> ptr1(new T()); // The construction method of this line is the same as that in 4.2, and the reference count is 1
std::shared_ptr<T> ptr2(ptr1);    // This is how to use the copy constructor, which will increase the reference count by 1

And, shared_ptr can be passed as a parameter of a function or returned as the return value of a function. This time is actually equivalent to using a copy constructor.

   The procedure is as follows: when making a function argument, the pointer execution copy constructor is passed into the function body, so the reference count of the memory block+1;When returned as a function value, the copy constructor passes the memory address to the new pointer and the reference count+1,Then, the local pointer performs destruct and reference count-1.     

4.4. From shared_ Return value construction of type conversion (cast) function provided by PTR

shared_ptr can also be type converted. For details of type conversion, see 6.1 below Assuming that B is a subclass of a, then, according to the knowledge in C + + inheritance and derivation, the pointer of B can of course be converted into the pointer of A. In the shared pointer, you should do this:

std::shared_ptr<B> ptrb(new B());
std::shared_ptr<A> ptra( dynamic_pointer_cast<A>(ptrb) );  //Essence or copy construction

5. shared_ "Assignment" of PTR

shared_ptr can also be assigned directly, but it must be assigned to shared of the same type_ PTR object, not the return value of ordinary C pointer or new operator. When the shared pointer A is assigned to b, if a is NULL, directly make a equal to b and add 1 to the reference count of the things they point to; If a also points to something, and if a is assigned to b, the reference count of the thing pointed to by a is reduced by 1, and the reference count of the newly pointed object is increased by 1 That is, the following codes are allowed:

std::shared_ptr<T> a(new T());
std::shared_ptr<T> b(new T());
a = b; // After that, the object originally referred to by a will be destroyed, and the object reference count referred to by b will be increased by 1

shared_ After the PTR object is constructed, it can be given a null value. At this time, the reset() function or nullptr should be used, such as:

std::shared_ptr<T> a(new T());
a.reset();        // After that, the reference count of the object originally referred to by a is - 1, and a becomes NULL. The memory will be destroyed here
a=nullptr;        //ditto. Recommended multipurpose

6. shared_ptr type conversion

shared_ptr has two types of conversion functions. One is static_pointer_cast, one is dynamic_pointer_cast. In fact, the usage is really similar to the static provided by C + +_ Cast and dynamic_cast is very similar, combined with 4.4 There is little to say about the code of and the following similar codes:

std::shared_ptr<A> ptra;
std::shared_ptr<B> ptrb(new B());
ptra = dynamic_pointer_cast<A>(ptrb);

7. From shared_ptr object gets the traditional C pointer

std::shared_ptr<T> ptr(new T());
T *p = ptr.get(); // Get traditional C pointer

It is recommended to use less get() function, because if it is in shared_ The delete function is called manually before PTR destruct, which will also lead to similar errors.

8. shared_ Incorrect usage of PTR

It must be noted that all methods described in this section are wrong!

8.1. Construct shared pointers "halfway" using traditional C pointers

The so-called midway means that the shared pointer is not constructed directly from the return value of new, such as constructing its own shared pointer from the this pointer.

8.2. Two or more shared pointers are constructed from the traditional C pointer of an object

In fact, this is similar to 8.1 It is also similar, or 8.1 For example, the following code is wrong:

T *a = new T();
shared_ptr<T> ptr1(a);
shared_ptr<T> ptr2(a);

In this way, the reference counts of ptr1 and ptr2 are calculated separately. When any of their objects is destructed, the object referred to by a will be destroyed, and a is a dangling pointer. Therefore, this object will be "destroyed twice". Therefore, an error is reported. (make_shared class template can be avoided)

8.3 do not construct shared with pointers in the stack_ PTR object

shared_ The default constructor of PTR uses delete to delete the associated memory, so the heap space pointer from new must also be used during construction. If it is a pointer in the stack, when shared_ When the PTR object exceeds the scope and calls the destructor delete memory, an error will be reported. (make_shared class template can be avoided)

9. Use of smart pointer

Based on the Boost library, C++11 adds shared_ptr and weak_ptr. They were first introduced in TR1, but in C++11, new functions were added on the basis of Boost

std::shared_ptr uses reference counting Every shared_ All copies of PTR point to the same memory In the last shared_ Memory is released only when PTR is destructed

std::shared_ptr<int> p1(new int(5));
std::shared_ptr<int> p2 = p1; // All point to the same memory
 
p1.reset(); // Because p2 is still there, the memory is not released
p2.reset(); // Free memory because there is no shared_ptr points to that memory
 
std::shared_ptr Use reference count, So there is the problem of cycle counting. To break the cycle,have access to std::weak_ptr. seeing the name of a thing one thinks of its function, weak_ptr Is a weak reference, Reference only, Do not count. If a piece of memory is shared_ptr and weak_ptr Simultaneous reference, When all shared_ptr After deconstruction,Whether or not weak_ptr Reference this memory, Memory will also be released. therefore weak_ptr There is no guarantee that the memory it points to must be valid, It needs to be checked before use.
 
std::shared_ptr<int> p1(new int(5));
std::weak_ptr<int> wp1 = p1; // Or only p1 has ownership
 
{
  std::shared_ptr<int> p2 = wp1.lock(); // Both p1 and p2 have ownership
  if(p2) // Check before use
  { 
    // Use p2
  }
} // p2 is destructed, and now only p1 has ownership
 
p1.reset(); // Memory was freed
 
std::shared_ptr<int> p3 = wp1.lock(); // Because the memory has been freed, you get a null pointer
if(p3)
{
  // I won't do that
}

One shared in multiple threads at the same time_ The PTR loop executes swap twice. shared_ The swap function of PTR is used to communicate with another shared_ptr exchanges reference objects and reference counts, which are write operations. After executing swap twice, shared_ The value of the object referenced by PTR should remain unchanged.

#include <stdio.h>
#include <tr1/memory>
#include <pthread.h>
 
using std::tr1::shared_ptr;
 
shared_ptr<int> gp(new int(2000));
 
shared_ptr<int>  CostaSwapSharedPtr1(shared_ptr<int> & p)
{
    shared_ptr<int> p1(p);
    shared_ptr<int> p2(new int(1000));
    p1.swap(p2);
    p2.swap(p1);
    return p1;
}
 
shared_ptr<int>  CostaSwapSharedPtr2(shared_ptr<int> & p)
{
    shared_ptr<int> p2(new int(1000));
    p.swap(p2);
    p2.swap(p);
    return p;
}
 
 
void* thread_start(void * arg)
{
    int i =0;
    for(;i<100000;i++)
    {
        shared_ptr<int> p= CostaSwapSharedPtr2(gp);
        if(*p!=2000)
        {
            printf("Thread error. *gp=%d \n", *gp);
            break;
        }
    }
    printf("Thread quit \n");
    return 0;
}
 
 
 
int main()
{
    pthread_t thread;
    int thread_num = 10, i=0;
    pthread_t* threads = new pthread_t[thread_num];
    for(;i<thread_num;i++)
        pthread_create(&threads[i], 0 , thread_start , &i);
    for(i=0;i<thread_num;i++)
        pthread_join(threads[i],0);
    delete[] threads;
    return 0;
}

I started 10 threads in this program. Each thread calls the CostaSwapSharedPtr2 function 100000 times. In the CostaSwapSharePtr2 function, for the same share_ptr global variable gp performs swap (write operation) twice. After the function returns, check whether the value of gp has been modified. If the gp value is modified, it is proved that multiple threads are connected to the same share_ptr is not safe to write.

The results of program operation are as follows:

Thread error. *gp=1000 
Thread error. *gp=1000 
Thread quit 
Thread quit 
Thread error. *gp=1000 
Thread quit 
Thread error. *gp=1000 
Thread quit 
Thread error. *gp=1000 
Thread quit 
Thread error. *gp=1000 
Thread quit 
Thread error. *gp=1000 
Thread quit 
Thread error. *gp=1000 
Thread quit 
Thread error. *gp=1000 
Thread quit 
Thread quit

There were 9 errors in 10 threads. Prove that multiple threads are connected to the same share_ptr is not safe to write.

What if we run CostaSwapSharedPtr1 instead of CostaSwapSharedPtr2 in the program?

The difference between CostaSwapSharedPtr1 and CostaSwapSharedPtr2 is that it does not directly write to the global variable gp, but copies the gp and then writes. The results of the operation are as follows:

Thread quit 
Thread quit 
Thread quit 
Thread quit 
Thread quit 
Thread quit 
Thread quit 
Thread quit 
Thread quit 
Thread quit

Reference link

  1. https://blog.csdn.net/yusiguyuan/article/details/22037833
  2. https://blog.csdn.net/aishuirenjia/article/details/91986961

Keywords: C C++ Visual Studio

Added by amitkrathi on Thu, 13 Jan 2022 20:05:26 +0200