C++中的智能指针分享

C++中的智能指针分享

学习内容:

  1. 什么是智能指针,有什么用
  2. 掌握 三种智能指针的定义,共享指针(std::shared_ptr)、唯一指针(std::unique_ptr)、弱指针(std::weak_ptr)
  3. 掌握具体的使用技巧和基本操作
  4. 掌握理解循环引用,并通过弱指针(std::weak_ptr)解决循环依赖的问题

智能指针

智能指针是一种在编程中用来管理动态分配内存的工具。传统的指针(例如在C语言中)允许程序员直接控制内存的分配和释放,但容易导致内存泄漏、野指针等问题。智能指针的目的是在一定程度上自动化这些管理过程,以减少错误的可能性。
智能指针通过在内部保持对动态分配内存的引用计数来工作。当创建一个智能指针时,它会分配一个堆内存块,并将引用计数初始化为1。当有新的智能指针指向相同的内存块时,引用计数会增加。当没有指针指向这块内存时,引用计数为0,这时内存块就会自动释放。

常见的智能指针类型包括:

1.共享指针(std::shared_ptr):允许多个指针共享同一块内存,并且会自动管理引用计数。当最后一个指针离开作用域时,内存才会被释放。
2. 唯一指针(std::unique_ptr):保证在任意时刻只有一个指针指向特定的内存块,因此没有引用计数开销。当唯一指针超出作用域时,它拥有的内存会被自动释放。
3. 弱指针(std::weak_ptr):与共享指针配合使用,用于解决循环引用问题。弱指针不会增加引用计数,也不会阻止内存的释放,但可以从共享指针获取数据,如果指向的对象已经被释放,则会返回空指针。

这些智能指针可以大大减少程序中的内存泄漏和野指针问题,提高了代码的可靠性和安全性。

其中:
1.内存泄漏:内存泄漏指的是在程序执行过程中,
动态分配的内存没有被释放,导致该内存无法再被程序所访问,
但又不断地占用系统的内存资源。
这种情况会导致程序占用的内存越来越多,最终可能导致系统性能下降甚至崩溃。
内存泄漏通常发生在程序未正确释放动态分配的内存,
或者在程序设计中存在循环引用导致的对象无法被垃圾回收。
下面展示一些 `问题代码片`。
#include <stdio.h>
#include <stdlib.h>

void memoryLeakExample() {
    int *ptr = (int *)malloc(sizeof(int)); // 分配动态内存
    // 没有释放内存
}

int main() {
    memoryLeakExample();
    // 在函数调用结束后,分配的内存没有被释放,发生了内存泄漏
    return 0;
} 
2.野指针:野指针指的是指向无效内存地址的指针。
通常情况下,野指针是指向已经释放的内存块或者未初始化的内存区域的指针。
当程序试图通过野指针访问内存时,通常会导致不可预测的行为,如程序崩溃、数据损坏等。
野指针的常见原因包括未初始化指针、指针指向的对象提前释放而未置空、指针操作不当等。
下面展示一些 `问题代码片`。
#include <stdio.h>

int main() {
    int *ptr; // 未初始化的指针
    printf("%d\n", *ptr); // 尝试访问未初始化的指针
    return 0;
} 

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *ptr = (int *)malloc(sizeof(int));
    *ptr = 5;
    free(ptr); // 释放内存
    printf("%d\n", *ptr); // 尝试访问已释放的内存,也可称作悬空指针
    return 0;
} 

智能指针如何定义声明

唯一指针(std::unique_ptr)
构造方法:
1、直接使用new关键字分配内存并将其传递给std::unique_ptr的构造函数。
2、使用std::make_unique函数创建。
#include <memory>

int main() {
    // 构造方法1:直接分配内存并传递给std::unique_ptr的构造函数
    std::unique_ptr<int> unique_ptr1(new int(42));

    // 构造方法2:使用std::make_unique函数创建
    auto unique_ptr2 = std::make_unique<int>(42);

    return 0;
} 
共享指针(std::shared_ptr)
构造方法:
直接使用new关键字分配内存并将其传递给std::shared_ptr的构造函数。
使用std::make_shared函数创建。
#include <memory>

int main() {
    // 构造方法1:直接分配内存并传递给std::shared_ptr的构造函数
    std::shared_ptr<int> shared_ptr1(new int(42));

    // 构造方法2:使用std::make_shared函数创建
    auto shared_ptr2 = std::make_shared<int>(42);

    return 0;
} 
弱指针(std::weak_ptr)
构造方法:
只能通过共享指针(std::shared_ptr)来构造弱指针(std::weak_ptr)。
#include <memory>

int main() {
    std::shared_ptr<int> shared_ptr(new int(42));

    // 使用共享指针构造弱指针
    std::weak_ptr<int> weak_ptr(shared_ptr); // 现在,weak_ptr 是对 shared_ptr 管理的相同资源的弱引用

    return 0;
} 

关于唯一指针的操作(std::unique_ptr)

#include <memory>
#include <iostream>

void processUniquePtr(std::unique_ptr<int> ptr) {
    // 这里 ptr 拥有资源的所有权
    // 在函数退出时,ptr 被销毁,资源也被释放
}

void processUniquePtr(int* ptr){
    //这里传递的是裸指针,避免了唯一指针的复制
}

void processUniquePtr(int& value){
    //这里传递的是内容的引用,只传递内容资源
}

void processUniquePtr(std::unique_ptr<int>& ptr){
    //这里传递的是唯一指针的引用,不用复制却可以可以修改内容 
}

unique_ptr<int> return_unique_ptr(int value){
	unique_ptr<int> ptr= std::make_unique<int>( value);
    return move(ptr);//这里可以返回唯一指针
}

int main() {
    std::unique_ptr<int> unique_ptr(new int(42));
	std::unique_ptr<int> ptr2;
	
	
	// 通过*运算符获取指向的值
	std::cout << *unique_ptr << std::endl;
	std::cout << *unique_ptr.get()<< std::endl;
	
	// 或者通过get()方法获取原始指针
	std::cout << unique_ptr.get() << std::endl;
	
	// 转移资源所有权为原始指针(普通指针)
	int* raw_ptr = unique_ptr.release();// 现在 unique_ptr 变成了一个空指针,但是 raw_ptr 指向了资源
	
	// 也可以使用 release() 之后的原始指针来创建新的唯一指针
	std::unique_ptr<int> new_unique_ptr(raw_ptr);
	
	// 通过移动赋值操作符(std::move)赋值
	unique_ptr = std::move(new_unique_ptr); // 现在unique_ptr 拥有资源,而 new_unique_ptr变成了一个空指针
	
	// 通过值传递
	processUniquePtr(std::move(unique_ptr)); // 现在 unique_ptr 变成了一个空指针
	
	//如果只是访问指针内容有以下操作
	ptr2= return_unique_ptr(42);
	
	//传递唯一指针ptr2的裸指针
	processUniquePtr(ptr2.get());
	
	//传递唯一指针ptr2的内容资源
	processUniquePtr(*ptr2);
	
	//访问并可以唯一指针内容有以下操作(引用作为参数不用复制)
	//传递唯一指针ptr2,作为参数:唯一指针引用
	processUniquePtr(ptr2);

    return 0;
} 

关于共享指针的操作:(std::shared_ptr)

#include <iostream>
#include <memory>

class Resource {
public:
    Resource() {
        std::cout << "Resource acquired\n";
    }
    ~Resource() {
        std::cout << "Resource destroyed\n";
    }
    void doSomething() {
        std::cout << "Resource being used\n";
    }
};

int main() {
    // 创建共享指针和唯一指针
    std::shared_ptr<int> shared_ptr(new int(42));
    std::unique_ptr<int> unique_ptr;

    // 将共享指针的所有权转移给唯一指针,注意move不进行检测,他只是把参数变成右值变可移动状态,结果是unique_ptr 指向资源的地址和shared_ptr之前的资源地址一样,没有进行复制。
    unique_ptr = std::move(shared_ptr);
    // 此时 shared_ptr 变为空指针,而 unique_ptr 指向动态分配的内存
    
	//上述操作的修改
	if (shared_ptr.use_count() == 1) {
    // 如果 shared_ptr 的引用计数为 1,表示它是资源的唯一所有者,可以安全地将其转移给 unique_ptr
    	unique_ptr = std::move(shared_ptr);
	} else {
	    // 如果 shared_ptr 的引用计数不为 1,表示资源还被其他指针共享,转移操作可能会导致问题
	    // 在这种情况下,输出错误信息,通知调用者转移操作不能执行
	    std::cerr << "Error: Cannot move shared_ptr to unique_ptr because it is not unique." << std::endl;
	    // 可以在这里采取其他适当的处理措施,比如抛出异常或者执行其他操作
	}

    // 将唯一指针的所有权转移给共享指针
    shared_ptr = std::move(unique_ptr);// 此时 unique_ptr 变为空指针,而 shared_ptr 指向动态分配的内存

	//与唯一指针不同的是,共享指针可以赋值操作,名字不一样,指向的同一个地址,不过每一次新建一个共享指针,都会在其内部引用计数器+1,删除或者释放则-1,当计数器为0则会释放资源
	// 将shared_ptr赋值给ptr2  //赋值操作和拷贝操作下面有进一步补充
	std::shared_ptr<int> ptr2 = shared_ptr;// shared_ptr和ptr2现在指向相同的内存块,并且共享引用计数
	cout<<*shared_ptr<<endl;
	*ptr2=0;
	cout<<*shared_ptr<<endl;
	
	//可以获取共享指针的地址,引用计数值,或者释放智能指针
	cout<<shared_ptr.get()<<endl;
	cout<<shared_ptr.use_count()<<endl; //获取共享指针的引用计数值
	ptr2.reset();//释放智能指针, ptr2=nullptr, 此时ptr2变为空指针
	cout<<shared_ptr.use_count()<<endl;
	
	//当然释放智能指针也可以同时指向别的智能指针
	std::shared_ptr<int> ptr1(new int(6));
	ptr1.reset(shared_ptr);// ptr1原有共享指针的计数器-1为0,ptr1指向为shared_ptr的资源并+1
	//注意我们可以使用delete ptr1.get() 不过会导致原来智能指针指向这个地址的未定义行为,所以用了智能指针就注意不要使用delete
	
	/*也可以用裸指针装共享指针的地址,
	建议不要这么做因为一旦共享指针引用计数器为0会导致资源释放,
	这个指向这块区域的裸指针会成为野指针,
	这个时候使用裸指针访问那块区域会变成未定义的行为。
	同理不能独占指针与其定义相违背。*/
	Int *p = shared_ptr.get();//用完就要赋空p=nullptr;
    
    /*
    拷贝操作:
	
	赋值操作:
	
	*/
	// 拷贝操作:
	// 创建一个原始指针并分配资源
    Resource* rawPtr = new Resource();

    // 创建第一个智能指针,进行拷贝操作
    std::shared_ptr<Resource> sharedPtr1(rawPtr);
    std::cout << "Shared pointer 1 count: " << sharedPtr1.use_count() << std::endl;//计数器为1
	// 使用完 rawPtr 后,将其赋值为 nullptr
	rawPtr = nullptr;
	
	// 创建第二个智能指针,并拷贝第一个智能指针
	std::shared_ptr<Resource> sharedPtr2(sharedPtr1);//拷贝构造函数会复制原始智能指针所管理的指针,使得新的智能指针与原始智能指针共享相同的资源,同时引用计数器增加
	std::cout << "Shared pointer 1 count after copy: " << sharedPtr1.use_count() << std::endl;//计数器为2
	std::cout << "Shared pointer 2 count: " << sharedPtr2.use_count() << std::endl;//计数器为2
	
	//赋值操作:
	// 创建第三个智能指针,并赋值第一个智能指针
	std::shared_ptr<Resource> sharedPtr3 = sharedPtr1;
	std::cout << "Shared pointer 1 count after copy: " << sharedPtr1.use_count() << std::endl;//计数器为3
	std::cout << "Shared pointer 2 count: " << sharedPtr2.use_count() << std::endl;//计数器为3
	std::cout << "Shared pointer 3 count: " << sharedPtr3.use_count() << std::endl;//计数器为3




    return 0;
} 
自定义删除器函数
#include <memory>
// 自定义删除器函数
void customDeleter(int *ptr) {
	std::cout << "Custom deleter is called" << std::endl;
	delete[] ptr; // 释放动态分配的数组
}

int main() {
	// 创建 shared_ptr,并指定自定义删除器
	std::shared_ptr<int> shared_ptr(new int[10], customDeleter);
	
	// 使用 shared_ptr
	for (int i = 0; i < 10; ++i) {
	    shared_ptr.get()[i] = i;
	}
	// shared_ptr 被销毁时,将调用自定义删除器释放数组内存
} 
//在多线程环境中,std::shared_ptr 可以用作多个线程之间共享对象的安全方式。
由于 std::shared_ptr 的引用计数是原子操作,
因此可以安全地在多个线程中增加或减少引用计数,而不需要额外的同步手段。
//在这个例子中,ptr 是一个 std::shared_ptr,它指向 SharedResource 对象。
线程 t1 和 t2 分别使用了这个 shared_ptr,而不需要额外的锁或同步机制来保护 ptr。
#include <memory>
#include <thread>
#include <iostream>

class SharedResource {
public:
	void doSomething() {
	    std::cout << "SharedResource::doSomething() executed\n";
	}
};

void threadFunction(std::shared_ptr<SharedResource> ptr) {
	ptr->doSomething();//(*ptr). doSomething();
}

int main() {
	std::shared_ptr<SharedResource> ptr = std::make_shared<SharedResource>();
	
	std::thread t1(threadFunction, ptr);
	std::thread t2(threadFunction, ptr);
	
	t1.join();
	t2.join();

	return 0;
} 

关于弱指针的操作:(std::weak_ptr)

弱指针的特点:
1.不拥有资源的所有权: 弱指针不会增加引用计数,也不会阻止资源的释放,
它只是共享指针所管理资源的一种观察。

2.无法直接访问资源: 弱指针无法直接访问共享资源,因为它不拥有资源的所有权,
必须先将弱指针转换为共享指针才能访问资源。转换为共享指针即引用计数器+1

3.用于解决循环引用问题: 主要用于解决共享指针之间可能产生的循环引用问题,
防止资源无法正确释放。
#include <memory>

int main() {
    std::shared_ptr<int> shared_ptr = std::make_shared<int>(42);

    // 定义弱指针,通过 shared_ptr 来构造
	std::weak_ptr<int> weak_ptr(shared_ptr);

	// 弱指针转换为共享指针
	std::shared_ptr<int> shared_ptr_from_weak = weak_ptr.lock();
	if (shared_ptr_from_weak) {
	    // 转换成功,可以安全地使用共享指针
	} else {
	    // 转换失败,弱指针无法锁定资源
	}

    return 0;
} 

循环引用:当两个或多个对象相互持有共享指针,形成循环引用时,可能导致内存泄漏。
使用std::weak_ptr打破循环引用,其中一个对象持有弱指针而不是共享指针。
#include <memory>
#include <iostream>

class B; // 前置声明

class A {
public:
    A() { std::cout << "A constructor\n"; }
    ~A() { std::cout << "A destructor\n"; }

    std::shared_ptr<B> b_ptr; // A持有B的共享指针
};

class B {
public:
    B() { std::cout << "B constructor\n"; }
    ~B() { std::cout << "B destructor\n"; }

    std::weak_ptr<A> a_ptr; // 打破循环引用:将B持有A的共享指针改为弱指针
};

int main() {
    std::shared_ptr<A> a_ptr = std::make_shared<A>(); // 创建A的共享指针
    std::shared_ptr<B> b_ptr = std::make_shared<B>(); // 创建B的共享指针

    // 互相持有对方的共享指针
    a_ptr->b_ptr = b_ptr;
    b_ptr->a_ptr = a_ptr;

    
    // 输出引用计数以及析构信息
    std::cout << "a_ptr use_count: " << a_ptr.use_count() << std::endl;
    std::cout << "b_ptr use_count: " << b_ptr.use_count() << std::endl;

    return 0;
} 

以上代码是我学习总结的,如有错误请指出。万分感谢!也祝大家学习愉快。

  • 19
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值