C++ Intelligent Pointer, Pointer Container Principle and Simple Implementation (auto_ptr,scoped_ptr,ptr_vector).

Keywords: C++

Catalog

C++ Intelligent Pointer, Pointer Container Principle and Simple Implementation (auto_ptr,scoped_ptr,ptr_vector).

Preface

Recently, another muduo asynchronous log touched many smart pointers, but did not plan to use the boost library, had to use a model.

The essence of smart pointers is to manage the lifecycle of data on the stack with objects on the stack.

Intelligent pointer itself is an object, it creates on the stack, allocates resources on the heap when it is constructed, and releases resources when it is destructed, thus avoiding the leakage of data resources on the heap.
At the same time, overload its - > and * operators to achieve the same operation as the bare pointer.

Let's look at the implementation code of several local smart pointer objects.

auto_ptr

Features of auto_ptr: Implement copy constructor, overload = operator, implement - >, * operator, so that it can be used as a common pointer.
At the same time, release() and reset() are used to transfer the right of use safely.

#ifndef _AUTO_PTR_HH
#define _AUTO_PTR_HH

template<typename T>
class auto_ptr{
public:
    explicit auto_ptr(T* p = 0):m_ptr(p){printf("1\n");
    }
    
    auto_ptr(auto_ptr& obj):m_ptr(obj.release()){printf("2\n");
    }
    
    auto_ptr& operator=(auto_ptr& obj){printf("3\n");
        reset(obj.release());
        return *this;
    }
    
    ~auto_ptr(){printf("4\n");
        delete m_ptr;
    }

    T* release(){
        T* tmp = m_ptr;
        m_ptr = 0;
        return tmp;
    }
    
    void reset(T* p){
        if(m_ptr != p)
            delete m_ptr;
        m_ptr = p;
    }
    
    T* get() const {
        return m_ptr;
    }
    
    T* operator->(){
        return get();
    }
    
    T& operator*(){
        return *get();
    }
    
private:
    T* m_ptr;
};

#endif

Test code:

#include "ScopePtr.hh"
#include "auto_ptr.hh"
#include <stdio.h>

class NonCopyable
{
protected: //Constructors can be called by derived classes, but cannot directly construct objects
    NonCopyable() {printf("Nocopy Constroctr\n");}
    ~NonCopyable() {printf("~Nocopy DeConstroctr\n");}
private:
    NonCopyable(const NonCopyable &);
    const NonCopyable &operator=(const NonCopyable &);
};


class Test// : private NonCopyable{
{public:
    Test(){printf("Constroctr\n");}
    ~Test(){printf("~DeConstroctr\n");}
};

int main(){
    
    //scoped_ptr<Test> st(new Test);
    
    auto_ptr<Test> ap1(new Test);
    auto_ptr<Test> ap2(new Test);

    auto_ptr<Test> ap3(ap2);
    
    ap2 = ap3;
    
    getchar();
    return 0;
}

Constroctr
1
Constroctr
1
2
3

4
4
~DeConstroctr
4
~DeConstroctr

scoped_ptr

This is what's inside the boost library. It's the opposite of auto_ptr: configuring copy constructs and = overloads to be private has achieved the goal of not allowing transfer of ownership.

#ifndef _SCOPE_PTR_HH
#define _SCOPE_PTR_HH
//  scoped_ptr mimics a built-in pointer except that it guarantees deletion
//  of the object pointed to, either on destruction of the scoped_ptr or via
//  an explicit reset(). scoped_ptr is a simple solution for simple needs;
//  use shared_ptr or std::auto_ptr if your needs are more complex.

/*
scoped_ptr Local smart pointers do not allow transfer of ownership.
*/
template <class T>
class scoped_ptr
{
public:
    scoped_ptr(T *p = 0) :m_ptr(p) {
    }
    
    ~scoped_ptr(){
        delete m_ptr;
    }
    
    T&operator*() const {
        return *m_ptr;
    }
    
    T*operator->() const {
        return m_ptr;
    }
    
    void reset(T *p)//Ownership is not allowed to be transferred, but smart pointers can be pointed to another space  
    {
        if (p != m_ptr && m_ptr != 0)
            delete m_ptr;
        m_ptr = p;
    }

    T* get(){
        return m_ptr;
    }

private://Set copy construction, assignment and judgement inequalities to private methods
    //Objects can no longer be invoked, that is, they can not be copied to construct and assign values, thus achieving the goal of not transferring ownership.
    scoped_ptr(const scoped_ptr<T> &y);
    scoped_ptr<T> operator=(const scoped_ptr<T> &);
    void operator==(scoped_ptr<T> const &) const;
    void operator!=(scoped_ptr<T> const &) const;

    T* m_ptr;
};

#endif

ptr_vector

This is also something inside boost. If we light object pointer into vector, container destruct will destruct the space opened up by itself to store pointer, but will not destruct the space pointed by pointer itself, so we have this container.

#ifndef _PTR_VECTOR_HH
#define _PTR_VECTOR_HH

#include "auto_ptr.hh"
#include <vector>

template<typename T>
class ptr_vector : public std::vector<T*>{
public:
    ~ptr_vector(){
        clear();
    }

    void clear(){
        typename std::vector<T*>::iterator it;
        for(it = std::vector<T*>::begin(); it != std::vector<T*>::end(); ++it){
            delete *it;//Release the memory pointed by the pointer.
        }
        
        /*
        for(size_t i = 0; i < std::vector<T*>::size(); ++i){
            delete std::vector<T*>::back();
        }*/
        
        std::vector<T*>::clear(); //Release the pointer itself.
    }

    typename std::vector<T*>::iterator erase(typename std::vector<T*>::iterator it){
        if(it >= std::vector<T*>::begin() && it < std::vector<T*>::end()){
            delete *it;
            std::vector<T*>::erase(it);
        }
    }

    void pop_back(){
        if(std::vector<T*>::size() > 0){
            delete std::vector<T*>::back();
            std::vector<T*>::pop_back();
        }
    }
    
    void push_back(T* const &v){
        auto_ptr<T> ap(v);
        std::vector<T*>::push_back(v);
        ap.release();
    }

    void push_back(auto_ptr<T> &v){
        std::vector<T*>::push_back(v.get());
        v.release();
    }
    
};

#endif

Test code:

class Test// : private NonCopyable{
{public:
    Test(int a = 99):a(a){printf("Constroctr\n");}
    ~Test(){printf("~DeConstroctr\n");}
    int get(){return a;}
private:
    int a;
};

int main(){
    auto_ptr<Test> ap1(new Test(0));
    auto_ptr<Test> ap2(new Test(1));
    auto_ptr<Test> ap3(new Test(2));

    printf("%d\n", ap1->get());
    
    ptr_vector<Test> apv;
    apv.push_back(ap1);
    apv.push_back(ap2);
    apv.push_back(ap3);
    printf("%d %lu \n", apv.front()->get(),apv.size());
/*
    apv.pop_back();
    printf("%lu\n", apv.size());

    apv.pop_back();
    printf("%lu\n", apv.size());
    
    apv.pop_back();
    printf("%lu\n", apv.size());
*/
    apv.pop_back();
    printf("%lu\n", apv.size());
    
    
    ptr_vector<Test>::iterator it = apv.begin();
    apv.erase(it);
    printf("%lu\n", apv.size());

    
    getchar();
    
    
    
    return 0;
}

Constroctr
Constroctr
Constroctr
0
0 3 
~DeConstroctr
2
~DeConstroctr
1

~DeConstroctr

This paper mainly introduces the essence of smart pointer, two simple implementations of smart pointer and the realization of a pointer container.

In fact, auto_ptr is not used much now. If the original pointer is not processed, after the transfer, the original pointer is empty. If someone uses it, it will cause problems.
There are many problems with vectors, pop_back() is an empty container, which will do the same thing inside the vector - size. At this time, the size of the container changes from zero to infinite, and the consequences are unpredictable. This case is handled in this example. An empty vector will do nothing. But the use of the vector is exquisite, otherwise it is easy to cause problems.

Posted by MaxD on Tue, 18 Dec 2018 09:06:04 -0800