【C++深度解析】25、C++中的智能指针

本文详细探讨了C++中智能指针的概念,包括shared_ptr、unique_ptr和weak_ptr的使用方法及实现原理。通过实例解释了如何避免内存泄漏和非法指针引用,以及循环引用问题的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

动态内存管理经常出现问题:一种是忘记释放内存,会造成内存泄漏;引用释放的内存,产生引用非法内存的指针。引入智能指针解决上面的问题,自动释放所指向的对象。shared_ptr 允许多个指针指向同一个对象,unique_ptr 则“独占”所指向的对象。weak_ptr 是一种弱引用,指向shared_ptr所管理的对象,这三种智能指针都定义在memory头文件中。

1 shared_ptr

1.1 shared_ptr 用法

  • 使用 make_shared 函数,此函数在动态内存中分配一个对象并初始化它,返回指向此对象的 shared_ptr。
  • 使用普通指针调用成员函数创建。
shared_ptr<int> p3 = make_shared<int>(42);

shared_ptr<int>pp(new int(10));

每个 shared_ptr 都有一个引用计数,我们拷贝一个 shared_ptr,计数加 1。当我们给 shared_ptr 赋予一个新值或是 shared_ptr 被销毁时,计数减 1,当计数器变为 0 时,自动释放所管理的对象。

  • shared_ptr 和 unique_ptr 都支持的操作

在这里插入图片描述

  • shared_ptr 独有的操作

在这里插入图片描述

  • 定义和更改 shared_ptr 的其他方法

在这里插入图片描述
使用注意
1、不要混合使用普通指针和智能指针
如果混合使用的话,智能指针自动释放之后,普通指针有时就会变成悬空指针,当将一个shared_ptr 绑定到一个普通指针时,应该使用 shared_ptr 进行操作,不应该再使用内置指针访问内存。

2、不能直接将指针赋值给 shared_ptr,他们一个是指针,一个是类对象

3、不要使用 get 初始化另一个智能指针或为智能指针赋值

shared_ptr<int> p(new int(42));	//引用计数为1
int *q = p.get();				//正确:但使用q时要注意,不要让它管理的指针被释放
{
    shared_ptr(q);				//未定义:两个独立的share_ptr指向相同的内存
}								//程序块结束,q 被销毁,它指向的内存被释放
int foo = *p;					//未定义,p指向的内存已经被释放了

p 和 q 指向相同的一块内存,由于是相互独立创建,各自的引用计数都是 1,当 q 所在的程序块结束时,q 被销毁,导致内存被释放,这时候 p 就变成一个空悬指针,使用将发生未定义的行为,当 p 被销毁时,这块空间会被二次 delete

1.2 实现原理

我们来看看是怎么实现的:

template<class T>
class SharedPtr					//模拟实现shared_ptr
{
public:
	SharedPtr(T* tmp = nullptr) : _ptr(tmp), count(new int(1)) { }

	~SharedPtr()
	{
		if (--(*count) == 0)
		{
			delete _ptr;
			delete count;
		}
	}

	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}
	
	SharedPtr(SharedPtr<T>& tmp) : _ptr(tmp._ptr), count(tmp.count)
	{
		(*count)++;
	}
	
	SharedPtr<T>& operator = (SharedPtr<T>& tmp)
	{
		if (_ptr != tmp._ptr)		//排除自己给自己赋值的可能
		{
			if (--(*count) == 0)	//先要判断原来的空间是否需要释放
			{
				delete _ptr;
				delete count;
			}
			_ptr = tmp._ptr;
			count = tmp.count;
			(*count)++;
		}
		return *this;				//考虑连等的可能
	}
private:
	T* _ptr;
	int* count;
};

这里先要说说记录引用计数为什么是指针:为了使各个对象都使用同一个变量标记,所以不能直接用整形记录。有人说那行,用个静态的吧,那也不行,用静态的就真正是所有变量都公用一个变量了.比如指针 a,b指向内存块 A,指针 c,d,e 指向内存块 B,本来 a,b 的引用计数应该是 2,c,d,e 的引用计数应该是 3,要使用静态的就都变成 5 了。这里要注意,所以最好的办法就是使用指针。

1.3 shared_ptr 所导致的循环引用问题

循环引用时会造成内存泄漏,首先我们来了解一下什么是循环引用,这里用链表的例子来实现一下:

struct ListNode
{
	int _data;
	shared_ptr<ListNode> _prev;
	shared_ptr<ListNode> _next;
 
	ListNode(int x)
		:_data(x)
		, _prev(NULL)
		,_next(NULL)
	{}
	~ListNode()
	{
		cout << "~ListNode" << endl;
	}
};
int main()
{
	shared_ptr<ListNode> cur(new ListNode(1));
	shared_ptr<ListNode> next(new ListNode(2));
	cur->_next = next;
	next->_prev = cur;
	cout << "cur" << "     " << cur.use_count() << endl;
	cout << "next" << "     " << next.use_count() << endl;
	return 0;
}

在这里插入图片描述
C++ 库为了解决这个问题,定义了 weak_ptr,专门用于辅助 shared_ptr 来解决引用计数的问题。当 shared_ptr 内部要监视其他的 shared_ptr 对象时,就采用 weak_ptr,不会使被监视的引用计数增加,且当被监视的对象析构后就自动失效。

2 unique_ptr

2.1 unique_ptr 的使用

某个时刻只能有一个 unique_ptr 指向一个给定对象,unique_ptr 不支持普通的拷贝或赋值操作。

  • unique_ptr 的操作

在这里插入图片描述
虽然我们不能拷贝或者赋值 unique_ptr,但是可以通过调用 release 或 reset 将指针所有权从一个(非const)unique_ptr 转移给另一个 unique_ptr

//将所有权从p1转移给p2,release将p1置为空
unique_ptr<string> p2(p1.release());

//将所有权从p3转移到p2,
unique_ptr<string>p3(new string("Trex"));
p2.reset(p3.release());

2.2 实现原理

unique_ptr 原理:直接把拷贝构造/赋值函数定义为 private,且只声明不实现。类成员无法调用这两个函数。

template<class T>
class UniquePtr					//模拟实现unique_ptr
{
public:
	UniquePtr(T* tmp = nullptr)	: _ptr(tmp)	{ }
	~UniquePtr()
	{
		if (_ptr)
			delete _ptr;
	}

	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}
private:
	UniquePtr(const UniquePtr<T>& tmp) = delete;
	UniquePtr<T>& operator=(const UniquePtr<T>& tmp) = delete;
	T* _ptr;
};

3 weak_ptr

weak_ptr 它指向 shared_ptr 管理的对象,weak_ptr 不改变引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,即使有weak_ptr指向对象,对象还是会被释放。

  • weak_ptr的操作

在这里插入图片描述

4 智能指针是线程安全的吗

  • 对于 unique_ptr,由于只是在当前代码块范围内有效。所以不涉及线程安全的问题。
  • 对于 shared_ptr,多个对象要同时共用一个引用计数变量,所以会存在线程安全的问题,但是标准库实现的时候考虑到了这一点,使用了基于原子操作(CAS)的方式来保证 shared_ptr 能够高效,原子的操作引用计数。

5 小结

1、有拷贝构造/赋值的情况,推荐使用 shared_ptr
2、不需要拷贝构造/赋值的时候,可以使用 unique_ptr
3、类内有访问其他 shared_ptr 对象时,使用 weak_ptr,可以不改变引用计数。

<think>好的,我现在需要帮用户在Ubuntu 24.04上安装和配置ktransformers。首先,我应该回忆一下ktransformers是什么。记得它是一个基于Hugging Face Transformers库的扩展,可能用于知识图谱相关的任务,或者特定领域的NLP应用。不过,具体细节可能需要查阅文档。 用户提到的是Ubuntu 24.04,所以第一步应该是确保系统环境正确。根据参考引用[^1],用户的操作系统是Ubuntu 24.04.1,可能需要先更新系统包。通常安装软件前更新系统是个好习惯,可以避免依赖问题。所以步骤可能包括运行sudo apt update和sudo apt upgrade。 接下来,安装Python环境。因为ktransformers可能是一个Python包,需要确认Python版本是否兼容。Ubuntu 24.04默认可能带有Python 3.10或更高版本,但需要检查。用户可能需要安装pip,以及venv来创建虚拟环境。虚拟环境能隔离项目依赖,避免冲突。 然后,安装PyTorch。因为Hugging Face Transformers通常依赖PyTorch或TensorFlow,而ktransformers可能基于PyTorch。所以需要访问PyTorch官网获取安装命令,选择适合的版本,比如稳定版,支持CUDA的版本如果用户有GPU的话。 安装Hugging Face Transformers库,使用pip install transformers。之后才是安装ktransformers。可能需要直接从GitHub仓库安装,如果它不在PyTorch库中。比如使用pip install git+https://github.com/用户名/ktransformers.git,但具体仓库地址需要确认是否正确。这里可能存在疑问,用户提供的ktransformers是否正确拼写?有没有可能拼写错误,比如应该是keras-transformers或者其他名称?如果找不到该库,可能需要用户确认名称或查找正确的安装方式。 配置部分可能涉及下载预训练模型,使用from ktransformers import...然后加载模型和分词器。需要示例代码,比如加载模型的AutoModel和AutoTokenizer,并给出一个简单的使用示例,如文本分类或生成任务。 验证安装是否成功,运行Python解释器,导入模块,看是否有错误。如果有错误,可能需要检查依赖项,比如是否缺少某些库,或者版本不匹配。这时候可能需要安装依赖,比如pip install numpy等。 另外,可能需要注意CUDA和cuDNN的安装,如果用户需要使用GPU加速。需要确保PyTorch正确识别CUDA,可以通过torch.cuda.is_available()来检查。 用户可能遇到的问题包括依赖冲突、Python版本不兼容、网络问题导致无法下载模型等。需要建议用户创建虚拟环境,逐步安装,并查看错误信息进行调试。 总结步骤应该是:系统更新→安装Python和pip→创建虚拟环境→安装PyTorch→安装Transformers→安装ktransformers→验证安装→示例代码。同时提醒用户注意版本兼容性,参考官方文档。</think>以下是在Ubuntu 24.04上安装和配置`ktransformers`的详细步骤: --- ### 1. 系统环境准备 首先更新系统软件包以确保环境最新: ```bash sudo apt update && sudo apt upgrade -y ``` 安装必要的编译工具和依赖项: ```bash sudo apt install -y python3-pip python3-venv git build-essential ``` --- ### 2. 创建Python虚拟环境 ```bash python3 -m venv ktransformers_env source ktransformers_env/bin/activate ``` --- ### 3. 安装PyTorch和依赖项 根据[PyTorch官方指南](https://pytorch.org/)选择对应版本(假设使用CPU版本): ```bash pip3 install torch torchvision torchaudio ``` --- ### 4. 安装Hugging Face Transformers ```bash pip3 install transformers ``` --- ### 5. 安装ktransformers 若`ktransformers`是公开库,直接通过pip安装: ```bash pip3 install ktransformers ``` 若需从GitHub安装(假设仓库地址为`https://github.com/xxx/ktransformers`): ```bash pip3 install git+https://github.com/xxx/ktransformers.git ``` --- ### 6. 验证安装 在Python解释器中运行: ```python from ktransformers import KTModel print("安装成功!") ``` --- ### 7. 配置示例代码 ```python from transformers import AutoTokenizer from ktransformers import KTModel model_name = "bert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(model_name) model = KTModel.from_pretrained(model_name) text = "Hello, ktransformers!" inputs = tokenizer(text, return_tensors="pt") outputs = model(**inputs) ``` --- ### 8. GPU加速支持(可选) 若使用NVIDIA GPU: 1. 确保已安装[NVIDIA驱动和CUDA](https://developer.nvidia.com/cuda-downloads) 2. 安装支持CUDA的PyTorch: ```bash pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 ``` --- ### 常见问题排查 1. **依赖冲突**:使用虚拟环境隔离依赖 2. **网络问题**:配置pip镜像源(如清华源) ```bash pip3 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple ``` 3. **模型下载失败**:通过`huggingface-cli`手动下载模型 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值