C++ 智能指针

1.why?

⾸先,说⼀下为什么要使⽤智能指针:智能指针其作⽤是管理⼀个指针,避免咋们程序员申请的空间在函数结束时忘记释放,造成内存泄漏这种情况滴发⽣。
然后使⽤智能指针可以很⼤程度上的避免这个问题,因为智能指针就是⼀个类,当超出了类的作⽤域是,类会⾃动调⽤析构函数,析构函数会⾃动释放资源。所以智能指针的作⽤原理就是在函数结束时⾃动释放内存空间,不需要⼿动释放内存空间。
 

内存泄漏举例

#include <iostream>
#include <string>
#include <memory>

using namespace std;


// 动态分配内存,没有释放就return
void memoryLeak1() {
	string *str = new string("动态分配内存!");// new 出来的是指针
	return;
}

// 动态分配内存,虽然有些释放内存的代码,但是被半路截胡return了
int memoryLeak2() {
	string *str = new string("内存泄露!");

	// ...此处省略一万行代码

	// 发生某些异常,需要结束函数
	if (1) {
		return -1;
	}
	/
	// 另外,使用try、catch结束函数,也会造成内存泄漏!
	/

	delete str;	// 虽然写了释放内存的代码,但是遭到函数中段返回,使得指针没有得到释放
	return 1;
}


int main(void) {

	memoryLeak1();

	memoryLeak2();

	return 0;
} 

2.what?

unique_ptr 

unique_ptr特性

  1. 基于排他所有权模式:两个指针不能指向同一个资源
  2. 无法进行左值unique_ptr复制构造,也无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值
  3. 保存指向某个对象的指针,当它本身离开作用域时会自动释放它指向的对象。
  4. 在容器中保存指针是安全的
// 1 禁止复制构造和=赋值

unique_ptr<string> p1(new string("I'm Li Ming!"));
unique_ptr<string> p2(new string("I'm age 22."));
	
cout << "p1:" << p1.get() << endl;// 获取原生指针,即一个地址
cout << "p2:" << p2.get() << endl;

p1 = p2;					// 禁止左值赋值
unique_ptr<string> p3(p2);	// 禁止左值赋值构造

unique_ptr<string> p3(std::move(p1));
// 可以进行所有权转移
p1 = std::move(p2);	// 使用move把左值转成右值就可以赋值了,效果和auto_ptr赋值一样

cout << "p1 = p2 赋值后:" << endl;
cout << "p1:" << p1.get() << endl;
cout << "p2:" << p2.get() << endl;



// 2 在容器里面不允许直接赋值

vector<unique_ptr<string>> vec;
unique_ptr<string> p3(new string("I'm P3"));
unique_ptr<string> p4(new string("I'm P4"));

vec.push_back(std::move(p3));
vec.push_back(std::move(p4));

cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;

vec[0] = vec[1];	/* 不允许直接赋值 */
vec[0] = std::move(vec[1]);		// 需要使用move修饰,使得程序员知道后果

cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;

// 3 会自动调用delete [] 函数去释放内存
unique_ptr<int[]> array(new int[5]);	// 支持这样定义

// 1 构造,并自定义析构
class Test {
public:
	Test() { cout << "Test的构造函数..." << endl; }
	~Test() { cout << "Test的析构函数..." << endl; }

	void doSomething() { cout << "do something......" << endl; }
};


// 自定义一个内存释放其
class DestructTest {
	public:
	void operator()(Test *pt) {
		pt->doSomething();
		delete pt;
	}
};

// 2 主动释放对象
unique_ptr<Test> t9(new Test);
t9 = NULL;
t9 = nullptr;
t9.reset();

// 3 放弃对象所有权
Test *t10 = t9.release();


问题

auto_ptr<string> p1;
string *str = new string("智能指针的内存管理陷阱");
p1.reset(str);	// p1托管str指针
{
	auto_ptr<string> p2;
	p2.reset(str);	// p2接管str指针时,会先取消p1的托管,然后再对str的托管
}

// 此时p1已经没有托管内容指针了,为NULL,在使用它就会内存报错!
cout << "str:" << *p1 << endl;

shared_ptr

熟悉了unique_ptr 后,其实我们发现unique_ptr 这种排他型的内存管理并不能适应所有情况,有很大的局限!如果需要多个指针变量共享怎么办?

如果有一种方式,可以记录引用特定内存对象的智能指针数量,当复制或拷贝时,引用计数加1,当智能指针析构时,引用计数减1,如果计数为零,代表已经没有指针指向这块内存,那么我们就释放它!这就是 shared_ptr 采用的策略!
 

// 1 构造
shared_ptr<Person> sp1;
Person *person1 = new Person(1);
sp1.reset(person1);	// 托管person1

shared_ptr<Person> sp2(new Person(2));
shared_ptr<Person> sp3(sp1);

不同初始化的性能差异

struct A;
std::shared_ptr<A> p1 = std::make_shared<A>();
std::shared_ptr<A> p2(new A);

上面两者有什么区别呢? 区别是:std::shared_ptr构造函数会执行两次内存申请,而std::make_shared则执行一次。

std::shared_ptr在实现的时候使用的refcount技术,因此内部会有一个计数器(控制块,用来管理数据)和一个指针,指向数据。因此在执行std::shared_ptr<A> p2(new A)的时候,首先会申请数据的内存,然后申请内控制块,因此是两次内存申请,而std::make_shared<A>()则是只执行一次内存申请,将数据和控制块的申请放到一起。那这一次和两次的区别会带来什么不同的效果呢?

void f(std::shared_ptr<Lhs> &lhs, std::shared_ptr<Rhs> &rhs){...}

f(std::shared_ptr<Lhs>(new Lhs()),
  std::shared_ptr<Rhs>(new Rhs())
);

因为C++允许参数在计算的时候打乱顺序,因此一个可能的顺序如下:

  1. new Lhs()
  2. new Rhs()
  3. std::shared_ptr
  4. std::shared_ptr

此时假设第2步出现异常,则在第一步申请的内存将没处释放了,上面产生内存泄露的本质是当申请数据指针后,没有马上传给std::shared_ptr。

weak_ptr

weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。 同时weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象。

弱指针的使用;
weak_ptr wpGirl_1; // 定义空的弱指针
weak_ptr wpGirl_2(spGirl); // 使用共享指针构造
wpGirl_1 = spGirl; // 允许共享指针赋值给弱指针

弱指针也可以获得引用计数;
wpGirl_1.use_count()

弱指针不支持 * 和 -> 对指针的访问;
 

shared_ptr<Girl> sp_girl;
sp_girl = wpGirl_1.lock();

// 使用完之后,再将共享指针置NULL即可
sp_girl = NULL;

3.how?

shared_ptr

// 定义一个名为 Type 的类
class Type
{
public:
    int a = 1; // 定义一个整数成员变量a,并初始化为1
};

// 定义一个名为 share_count 的类,用于跟踪共享计数
class share_count {
public:
    share_count() : _count(1) {} // 构造函数,初始化共享计数为1
    void add_count() {
        ++_count; // 增加共享计数
    }
    long reduce_count() {
        return --_count; // 减少共享计数并返回新值
    }
    long get_count() const {
        return _count; // 获取当前共享计数的值
    }
private:
    long _count; // 保存共享计数的私有成员变量
};

// 定义一个模板类 smart_ptr,用于管理动态分配的对象
template<typename T>
class smart_ptr
{
public:
    smart_ptr(T* ptr = NULL) : m_ptr(ptr) {
        if (ptr) {
            m_share_count = new share_count; // 如果指针非空,创建一个共享计数对象
        }
    }
    ~smart_ptr() {
        if (m_ptr && !m_share_count->reduce_count()) {
            delete m_ptr; // 如果计数归零,删除对象
            delete m_share_count; // 删除共享计数对象
            cout << "~smart_ptr" << endl; // 输出析构消息
        }
    }
    T& operator*() const { return *m_ptr; } // 解引用操作符,函数被定义为只读的
    T* operator->() const { return m_ptr; } // 成员访问操作符
    operator bool() const { return m_ptr; } // 类型转换为布尔值,用于检查指针是否有效

    // 性能优化: 编译器在处理 noexcept 函数时可以进行一些性能优化,因为它们知道这些函数不会引发异常。
    // 这可以在某些情况下提高程序的性能。
    // 拷贝构造函数,实现共享计数的复制
    smart_ptr(const smart_ptr& rhs) noexcept {
        m_ptr = rhs.m_ptr;
        m_share_count = rhs.m_share_count;
        m_share_count->add_count(); // 增加共享计数
    }
    
    // 拷贝赋值运算符,实现共享计数的复制
    // 在拷贝赋值运算符中,通常返回引用的原因是为了支持链式赋值。
    smart_ptr& operator=(const smart_ptr& rhs) noexcept {
        m_ptr = rhs.m_ptr;
        m_share_count = rhs.m_share_count;
        m_share_count->add_count(); // 增加共享计数
        // 返回当前对象的引用
        return *this;
    }
    
    // 获取当前共享计数的值
    long use_count() const {
        if (m_ptr) {
            return m_share_count->get_count();
        }
        return 0;
    }
private:
    T* m_ptr; // 指向动态分配对象的指针
    share_count* m_share_count; // 指向共享计数对象的指针
};

int main()
{
    smart_ptr<Type> sptr(new Type); // 创建智能指针,管理一个 Type 对象
    cout << "sptr的共享计数:" << sptr.use_count() << endl;

    smart_ptr<Type> sptr2(sptr); // 使用拷贝构造函数创建另一个智能指针,共享同一个对象
    cout << "sptr2的共享计数:" << sptr2.use_count() << endl;

    smart_ptr<Type> sptr3; // 创建另一个智能指针
    sptr3 = sptr2; // 使用拷贝赋值运算符,共享同一个对象
    cout << "sptr3的共享计数:" << sptr3.use_count() << endl;
    return 0;
}

// 输出示例:
// sptr的共享计数:1
// sptr2的共享计数:2
// sptr3的共享计数:3
// ~smart_ptr:析构消息,当 sptr、sptr2 和 sptr3 超出作用域时触发析构函数

参考

C++ 智能指针 - 全部用法详解-CSDN博客

  • 23
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值