C++智能指针

C++智能指针

初识智能指针

智能指针和裸指针,平时使用的指针就是智能指针,类似于int *p=new int(10);,罗指针存在一个问题,程序没有释放裸指针,那么就会导致内存的泄露。智能指针能自动管理好指针的开辟,指针的释放,保证做到资源的自动释放。(其实也就是利用栈上对象出作用域自动析构的特征,来做到资源的自动释放)

#include 
using namespace std;
// 智能指针,使用类来管理指针,在作用域结束后,指针会自动析构
template <typename T>
class SmartPointer
{
public:
    SmartPointer(T *ptr = nullptr) : mptr(ptr){};
    ~SmartPointer() { delete mptr; mptr=nullptr; }
    T &operator*() { return *mptr; }
    T *operator->() { return mptr; }
    // 防止使用new智能指针导致内存泄漏
    void *operator new(size_t s) = delete;
    void *operator new[](size_t s) = delete;
private:
    T *mptr;
};
int main()
{
    // 裸指针
    int *p = new int(10);

    SmartPointer<int> ptr(new int(10));
    *ptr = 20; // *ptr=20; => (operator*())=20;
    class Test
    {
    public:
        void test() { cout << "-----" << endl; }
    };
    SmartPointer<Test> t(new Test());
    t->test(); // t->test() => (operator->())->test();
    return 0;
}

所以这里就存在一个问题,能不能使用一个SmartPointer ptr=new SmartPointer();来初始化一个智能指针,将对象存储在栈上?
结果肯定是不能的,上述代码尽管在语法上是没有问题的,但是智能指针利用的就是对象在栈上,出作用域进行析构,所以就会自动释放开辟的资源,现在使用一个new开辟智能指针的对象,这就是本末倒置。

不带引用计数的智能指针

不带引用计数的智能指针:只有一个智能指针能管理资源。

上述代码存在一个问题,就是当使用一个SmartPointer拷贝构造函数时,mptr会指向同一片内存,当两个对象开始析构时,会导致内存的重复释放。

SmartPointer<int> p1=SmartPointer(new int(10));
SmartPointer<int> p2(p1);//发生浅拷贝,p1在析构的时候,mptr已经被释放了。

对于这种普通的构造函数浅拷贝来说,直接重写拷贝构造函数就可以解决,在类的实现中添加:

SmartPointer(const SmartPointer<T> &src) { mptr = new T(*src.mptr); }

但是如果用户要的就是p1和p2就是需要指向同一块内存空间,那么就不能使用自定义拷贝构造函数,这样就曲解了智能指针的作用。

不带引用计数的智能指针有:auto_ptr(C++库中包含的)、scope_ptrunique_ptr(后面两个都是C++11添加的)。如何解决浅拷贝问题?

智能指针头文件在memory中。

auto_ptr

auto_ptr(auto_ptr& _Right) noexcept : _Myptr(_Right.release()) {}
_Ty* release() noexcept {
    _Ty* _Tmp = _Myptr;
    _Myptr    = nullptr;
    return _Tmp;
}
_Ty* _Myptr;

使用auto_ptr的拷贝构造函数时,会先将原来的地址先保存,然后断开原来的指针,再将当前的指针指向原来的地址。简而言之,就是把原来对象的资源赋给当前对象,原来对象的东西全部被释放了,只有最后的对象存在内容。

不推荐容器中使用auto_ptr,就会导致元素的释放。一个容器给另一个容器赋值,导致第一个容器中的值全部被释放了。

scope_ptr

scope_ptr内部删除了拷贝构造函数和赋值操作符重载函数。

scope_ptr(const scope_ptr<T> &)=delete;
scope_ptr<T>& operator=(const scope_ptr<T> &)=delete;

所以scope_ptr资源一直存放再第一个对象上,防止资源被释放。

unique_ptr

unique_ptrscope_ptr都删除了拷贝构造函数和操作符重载函数,但是实现了右值引用参数的构造函数和赋值操作符重载函数。所以可以使用右值操作符重载函数和右值引用的拷贝构造函数进行unique_ptr智能指针的使用。

unique_ptr(const scope_ptr<T> &)=delete;
unique_ptr(const scope_ptr<T> &&);
unique_ptr<T>& operator=(const unique_ptr<T> &)=delete;
unique_ptr<T>& operator=(const unique_ptr<T> &&);

但是unique_ptr支持右值引用的实现,类似于:

unique_ptr<int> p1(new int(10));
//unique_ptr p2(p1);//不支持,删除了拷贝构造函数
unique_ptr<int> p2(std::move(p1));//强制将p1转化为右值,move函数的作用:将一个左值转化为右值
//但是这样也会将p1的资源转移到p2,所有后面不要继续访问p1,因为p1资源已经被释放了。

带引用计数的智能指针

带引用计数的智能指针:多个智能指针可以管理同一个资源。给每一个对象资源匹配一个引用计数。

智能指针 -> 存在资源的使用 -> 引用计数+1。
智能指针 -> 不使用资源的时候(释放) -> 引用计数-1 -> 如果引用计数为0,就需要释放资源。

这里实现了一个自定义的带计数器的智能指针:

#include 
#include
using namespace std;

template <typename T>
class RefCnt
{
public:
    RefCnt(T* ptr = nullptr) : mptr(ptr)
    {
        if (mptr != nullptr)
        {
            mcount = 1;
        }
    }
    void addRef()
    {
        ++mcount;
    }
    int delRef()
    {
        mcount--;
        return mcount;
    }
private:
    T* mptr;
    int mcount;
};
template <typename T>
class SmartPtr
{
public:
    SmartPtr(T* ptr = nullptr) : mptr(ptr)
    {
        mpRefCnt = new RefCnt<T>(mptr);
    }
    ~SmartPtr()
    {
        if (mpRefCnt->delRef() == 0)
        {
            delete mptr;
            mptr = nullptr;
        }
    }
    T& operator*() { return *mptr; }
    T* operator->() { return mptr; }
    SmartPtr(const SmartPtr<T>& src) : mptr(src.mptr), mpRefCnt(src.mpRefCnt)
    {
        if (mptr != nullptr)
        {
            mpRefCnt->addRef();
        }
    }
    // 将src的资源转到等式左边的对象上,所以右边的src对象如果引用计数为1那么就应该让资源释放
    // 这里好像存在一个bug,当左值是一个mptr=nullptr时,那么本身引用计数就为0,所以不会触发第二个判断
    // 本意应该是左值如果引用计数为1,那么就让其释放,可能没考虑到这种
    SmartPtr<T>& operator=(const SmartPtr<T>& src)
    {
        if (this == &src)
        {
            return *this;
        }
        if (mpRefCnt->delRef() == 0)
        {
            delete mptr;
        }
        mptr = src.mptr;
        mpRefCnt = src.mpRefCnt;
        mpRefCnt->addRef();
        return *this;
    }

private:
    T* mptr;          // 指向资源的指针
    RefCnt<T>* mpRefCnt; // 指向该资源引用计数对象的指针
};


int main()
{
    SmartPtr<int> p1(new int(10));
    SmartPtr<int> p2(p1);
    cout << *p1 << "-------" << *p2 << endl;
    SmartPtr<int> p3;
    p3 = p1;
    cout << *p3 << endl;
    cout << *p1 << endl;
    return 0;
}

上述智能指针在多线程环境中存在一个问题,就是引用计数的mcount不是线程安全的,在真实环境中shared_ptr/weak_ptr是线程安全的,其实也就是将int改为atomic_int类型,表示该计数器可是原子类型的,只能一个线程访问。

weak_ptr

弱智能指针,不会改变资源的引用计数。是一种观察者模式,资源可能被释放了。所以在使用的时候需要将其转化为强智能指针。

shared_ptr

强智能指针,会改变资源的引用计数。

shared_ptr交叉引用

强智能指针会存在循环引用(交叉引用)的问题。会出现什么样的结果?怎么解决?

交叉引用案例:

class B;
class A
{
public:
    A() { cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }
    shared_ptr<B> _ptrb;
private:
};
class B {
public:
    B() { cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }
    shared_ptr<A> _ptra;
};
int main()
{
    //shared_ptr
    shared_ptr<A> pa(new A());
    shared_ptr<B> pb(new B());

    // 添加之后就会出现问题,导致资源无法释放(因为添加指针的时候会出现引用计数+1的情况,
    // 在结束作用域之后,其实引用计数为1,导致没有释放资源)
    pa->_ptrb = pb;
    pb->_ptra = pa;

    cout << pa.use_count() << endl;
    cout << pb.use_count() << endl;
    return 0;
}

解决方法:定义对象的时候使用强智能指针,引用对象的地方使用弱智能指针。
交叉引用案例:

class B;
class A
{
public:
    A() { cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }
    weak_ptr<B> _ptrb;
private:
};
class B {
public:
    B() { cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }
    weak_ptr<A> _ptra;
};
int main()
{
    //shared_ptr
    shared_ptr<A> pa(new A());
    shared_ptr<B> pb(new B());

    // 添加之后就会出现问题,导致资源无法释放(因为添加指针的时候会出现引用计数+1的情况,
    // 在结束作用域之后,其实引用计数为1,导致没有释放资源)
    pa->_ptrb = pb;
    pb->_ptra = pa;

    cout << pa.use_count() << endl;
    cout << pb.use_count() << endl;
    return 0;
}

但是如果在类内部存在指针互相调用的情况,那么弱智能指针会存在无法调用函数的情况(弱智能指针是一个观察者角色,需要使用lock()方法转化为强智能指针),类似以下情况:

class B;
class A
{
public:
    A() { cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }
    void test()
    {
        cout << "A::test()" << endl;
    }
    weak_ptr<B> _ptrb;
private:
};
class B {
public:
    B() { cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }
    void test()
    {
        //_ptra->test();// 无法直接调用A的函数,因为这个时候_ptra是弱智能指针,是一个观察者角色
        shared_ptr<A> ps = _ptra.lock();
        if (ps != nullptr)
        {
            ps->test();
        }    
    }
    weak_ptr<A> _ptra;
};

多线程访问共享对象的线程安全问题

子线程在等待后,资源已经被释放了,导致无法调用函数完成,程序执行和实际不一致。如下示例所示:

#include
#include
#include
using namespace std;

class A
{
public:
	A() { cout << "A()" << endl; }
	~A() { cout << "~A()" << endl; }
	void test() { cout << "A::test()" << endl; }
};
void handler01(A* p)
{
	p->test();
}
void handler02(weak_ptr<A> q)
{
	// 让子线程沉睡2s,沉睡2s之后,子线程可能已经被释放了
	std::this_thread::sleep_for(chrono::seconds(2));
	shared_ptr<A> ps = q.lock();
	if (ps != nullptr)
	{
		ps->test();
	}
	else
	{
		cout << "q resource is deleted!" << endl;
	}
}
int main()
{	
	A* p = new A();
	thread th(handler01, p);
	delete p;
	th.detach();
	{
		shared_ptr<A> p(new A());
		thread th(handler02, weak_ptr<A>(p));
		th.detach();
	}
	std::this_thread::sleep_for(chrono::seconds(5));
	return 0;
}

C++智能指针_第1张图片
子线程中对象被释放了,导致无法调用test()函数,所以没有正常打印结果。

智能指针删除器

智能指针的删除器 deletor

智能指针:能够保证资源绝对的释放,delete ptr; delete []ptr;

自定义指针删除器示例代码,作为第二个参数传入智能指针中:

template<typename T>
class MyDeletor
{
public:
	void operator()(T* ptr) const
	{
		delete[]ptr;
	}
};
template<typename T>
class MyFileDeletor
{
public:
	void operator()(T* ptr) const
	{
		fclose(ptr);
	}
};
int main()
{
	unique_ptr<int, MyDeletor<int>> ptr1(new int[10]);
	unique_ptr<FILE, MyFileDeletor<FILE>> ptr2(fopen("data.txt","r"));
	return 0;
}

但是也存在着一个问题,对于一个类型,可能需要自定义一种类型的删除器。

所以就可以使用lambda表达式联合使用:

unique_ptr<int, function<void(int*)>> ptr3(new int[10], [](int* p)->void {
	delete[]p;
});
unique_ptr<FILE, function<void(FILE*)>> ptr4(fopen("data.txt", "r"), [](FILE* p)->void {
	fclose(p);
});

你可能感兴趣的:(C++学习笔记,c++,开发语言,智能指针)