C++ 智能指针

智能指针背景

 智能指针是针对内存泄漏的问题进行处理。

 下面是一个内存泄漏的场景:

场景1

场景2

我们写一个模拟除法函数,调用一下。该函数会对除数为0的情况抛异常:

#include<iostream>
using namespace std;

double chu(int a, int b)
{
	if (b == 0)
	{
		throw invalid_argument("除数不能为0!");
	}
	else
	{
		return (double)a / b;
	}
}
void funb() 
{
	int* p = new int[10];
	int a = 0, b = 0; cin >> a >> b;
	cout << chu(a, b) << endl;
	delete[] p;
	cout << "deletc[]" << p << endl;
}
	
int main()
{
	try
	{
		funb();
	}
	catch (const exception &e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

 抛异常不调用析构:

正常情况会释放:

 我们发现如果没有抛异常就程序最后释放(调用析构),如果抛异常了那么最后不会析构(不调用析构)。

那么如果遇到抛异常的情况,没有调用析构,最后可能会导致内存泄漏的问题。

这个时候就可以用聪明的指针来解决

智能指针一般都是一个模板类,如下:

创建一个SmartPtr.h文件:

SmartPtr.h

#pragma once

template<typename T>
class SmartPtr {
public:
    SmartPtr(T* ptr) : _ptr(ptr) {};
    ~SmartPtr() 
    {
        cout << "deleting SmartPtr" << endl;
        delete _ptr; 
    }

private:
    T* _ptr;
};

我们在主文件里声明一个SmartPtr类型的对象,把delete交给p1这个对象去管理,那么无论如何最后都会调用析构,进行释放。


#include<iostream>
using namespace std;
#include"smartptr.h"
int main()
{
	SmartPtr<int> p1(new int);
	return 0;
}

RAII

智能指针的思想就是RAII,也就是资源获取即初始化

上述的 SmartPtr 还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可
以通过 -> 去访问所指空间中的内容。
所以我们可以对*,->进行一下重载。
SmartPtr.h
    T& operator*() const { return *_ptr; }
    T* operator->() const { return _ptr; }

那么重载*,->的场景用于哪里呢?比如说我们就可以像正常访问指针一样去访问了:

test.cpp

	SmartPtr<int> p1(new int(10));
	cout << *p1 << endl;

其次我们也可以用于键值对的场景:

	SmartPtr<pair<string, int>>p(new pair<string, int>("hello", 10));
	p->first = "world";
	p->second = 20;
	cout<<(p->second+=1)<<endl;

其实就相当于
cout << (p.operator->()->second += 1) <<endl;

系统提供的智能指针

auto_ptr

我们知道两个对象管理同一块地址会出问题,但是auto_ptr提供了管理权转移这么一个策略:

即:把管理权限给最后一个对象后,前面的对象置空,由最后一个对象管理权限。

如下所示就是p1把管理权限给了p2:

缺点:

前面的对象把权限给最后一个对象后,前面的对象都置空了。假设用户不懂的话对前面的对象进行操作,就会造成对空指针进行操作,就会段错误:

unique_ptr

既然拷贝有问题(管理权转移,导致前面的对象悬空),那我干脆不让你拷贝了。

因此引入第二个指针:unique_ptr。

怎样才能让不被拷贝呢?把拷贝构造和赋值重载函数给禁掉就行了(c++98是把这俩函数谁为私有,c++11是给这俩函数设delete)。

介绍

  1. 独占所有权

    • unique_ptr实现独占所有权语义,即在任何时刻,只能有一个unique_ptr指向一个动态分配的资源对象。这意味着不能有两个unique_ptr同时管理同一个资源。
  2. 不可复制

    • unique_ptr不支持拷贝操作,无法通过复制构造函数或赋值操作来复制unique_ptr34。
  3. 移动语义

    • 虽然不支持拷贝,但unique_ptr支持移动操作。可以使用std::move函数将一个unique_ptr的所有权转移给另一个unique_ptr25。
  4. 自动释放资源

    • unique_ptr超出作用域或被销毁时,它所管理的资源会自动被释放,从而防止内存泄漏5。
  5. 自定义删除器

    • unique_ptr允许自定义删除器,可以在释放资源时执行特定操作12。

使用方法

#include <iostream>
#include <memory>

class Widget {
public:
    Widget(int x, int y, int z) : m_x(x), m_y(y), m_z(z) {
        std::cout << "Widget constructed\n";
    }
    void print() {
        std::cout << m_x << "," << m_y << "," << m_z << std::endl;
    }
    ~Widget() {
        std::cout << "Widget destroyed\n";
    }

private:
    int m_x, m_y, m_z;
};

int main() {
    {
        std::unique_ptr<Widget> w1(new Widget(1, 2, 3));
        w1->print();
        auto w2 = std::move(w1);
        if (w1 == nullptr) {
            std::cout << "w1 is nullptr\n";
        }
        w2->print();
    } // w2被销毁,释放Widget对象

    std::cout << "End of main\n";
    return 0;
}

在这个示例中,w1最初管理一个Widget对象,然后通过std::move将所有权转移给w2。当w2超出作用域时,Widget对象被自动销毁。

通过使用unique_ptr,可以有效地管理动态分配的内存,避免内存泄漏和其他与内存管理相关的问题。

release()函数返回裸指针,释放所有权。

正常情况下裸指针不能和智能指针混合:

正确的应该是用智能指针接收智能指针。但是通过release()函数可以让裸指针接收unique_ptr的返回值,并且获得unique_ptr对象的资源:

shared_ptr(共享指针)

shared_ptr还可以像我们之前自己写的智能指针一样去使用:

	shared_ptr<int> p1(new int(10));
	cout << *p1 << endl;

它的特性如下:

	shared_ptr<int> p1(new int(10));
	shared_ptr<int> p2 = p1;
	cout << *p1 << " " << *p2 << endl;

如上所示,是否会报错?p1会自动释放,那么p2就指向已经释放的地址了,变成野指针了。

但是实际上程序是正常运行的: 

这是因为智能指针引入了"引用计数"的技术。就是统计有多少个指针指向了该对象。p1,p2都指向了该地址,那么计数器就是2,每delete一个指针,计数器就会减1,只有当计数器数值为0时才会释放这块地址。

 use_count()函数可以显示当前计数器个数:

	shared_ptr<int> p1(new int(10));
	cout<<"计数:"<<p1.use_count() << endl;
	shared_ptr<int> p2 = p1;
	cout<< "计数:" << p2.use_count()<<endl;
	cout << *p1 << " " << *p2 << endl;

 通过rest()函数可以释放该指针:

	shared_ptr<int> p1(new int(10));
	cout<<"计数:"<<p1.use_count() << endl;
	shared_ptr<int> p2 = p1;
	p1.reset();
	cout<< "计数:" << p2.use_count()<<endl;
	cout << *p1 << " " << *p2 << endl;

 p1已经释放了就不能再访问这块空间了,所以cout<<*p1<<" "<<*p2<<endl;就会造成段错误。

 unique()函数用来检查当前指针是否为独占指针,也就是计数器是否为1.

	shared_ptr<int> p1(new int(10));
	cout<<"计数:"<<p1.use_count() << endl;
	shared_ptr<int> p2 = p1;
	p1.reset();
	cout<< "计数:" << p2.use_count()<<endl;
	if (p2.unique())cout << "p2是独占指针" << endl;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孙鹏宇.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值