Tars源码分析---智能指针的实现

本文介绍了Tars中实现的三种智能指针:TC_AutoPtr、TC_SharedPtr和TC_ScopedPtr。详细分析了这些智能指针的内部实现机制,包括构造函数、赋值重载和析构函数等关键部分。此外,还讨论了引用计数在对象生命周期管理中的作用。

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

前言

Tars主要提供了三种类型的智能指针:TC_AutoPtr,TC_SharedPtr,TC_ScopedPtr。对智能指针的合适使用可以避免内存泄漏等问题。本文将分别对这三种智能指针在tars中的实现进行分析。

TC_AutoPtr
TC_HandleBaseT

在tars的设计中,所有想要使用TC_AutoPtr来管理的对象都需要继承TC_HandleBaseT。TC_HandleBaseT中核心是一个计数器:

    atomic_type   _atomic;

也就是说,所有使用TC_AutoPtr管理的对象都会隐含一个计数器在自己对象所属的内存中,该计数器记录了该对象被多少个不同的变量引用。当计数器值为0时,表示该对象对应的内存已经没有再被引用,此时就会被回收。因此TC_AutoPtr的核心实现就是对对象中的_atomic如何进行操作。下面我们介绍TC_AutoPtr相关函数的实现。

构造函数

TC_AutoPtr是提供了三个构造函数:

TC_AutoPtr(T* p = 0)
TC_AutoPtr(const TC_AutoPtr<Y>& r)
TC_AutoPtr(const TC_AutoPtr& r)

第一个构造函数表示可以直接使用原生对象指针对TC_AutoPtr进行初始化。
第二和第三个构造函数是使用其他智能指针进行构造。
这三个构造函数都做两件事情:

  1. 将原生指针放到TC_AutoPtr智能指针对象中。
  2. 增加使用智能指针的对象的引用计数

这里之所以要将原生对象指针放到TC_AutoPtr中,主要还是为了可以在TC_AutoPtr中方便操作该指针指向的对象的引用计数器。

赋值重载

实现智能指针的另一个需要完成的就是重载赋值运算符了。这主要是因为赋值可能会使得TC_AutoPtr本来指向的对象内存被回收,以及新指向的对象引用计数增加。下面看一下赋值运算符的重载实现。

TC_AutoPtr也是提供了三种赋值,可以分别支持对原生指针赋值,TC_AutoPtr对象赋值,其他类型的TC_AutoPtr对象赋值。

赋值运算主要也是做两件事:

  1. 修改TC_AutoPtr指向的对象指针
  2. 修改引用计数

其中,由于赋值涉及到=运算符左右两个对象,因此会引起两个对象的引用计数的修改。在赋值运算中唯一需要注意的是检查赋值的两个运算对象是否是相同的对象,还有一点是赋值时是有可能引起引用计数减为零,导致对象被析构的,这个动作会在TC_HandleBaseT的decRef()函数中完成。

析构函数

TC_AutoPtr的析构函数一般会在TC_AutoPtr对象走出当前作用域时被自动调用,而走出作用域以为着用户将不再使用这个TC_AutoPtr,因此它所指向的对象的引用计数应该减一

    ~TC_AutoPtr()
    {
        if(_ptr)
        {
            _ptr->decRef();
        }
    }

虽然简单,但这正是c++实现智能指针的前提。

TC_ScopedPtr

TC_ScopedPtr比TC_AutoPtr对对象的管理要严格的多:

    private:
        TC_ScopedPtr(const TC_ScopedPtr&);
        void operator=(const TC_ScopedPtr&);

它不允许拷贝构造和赋值。也就是说TC_ScopedPtr要求每一个对象只能有一个TC_ScopedPtr对它进行管理,当TC_ScopedPtr走出作用域时就会析构这个对象。当然,它不能避免用户进行如下的操作:

T* Ptr;
TC_ScopedPtr tmp1(Ptr);
TC_ScopedPtr tmp2(Ptr);

这样的话,就会导致Ptr被析构两次,出现未定义的行为。为了避免这种问题,在使用智能指针时应该避免同一个原生指针多次使用,亦即在构造智能指针之后,对原生指针的所有使用都应该基于这个智能指针才行,这样才符合语义。

TC_ScopedPtr虽然独占指针控制权,但是它支持控制权转移,这在TC_ScopedPtr的swap函数中有实现。

TC_SharedPtr

TC_SharedPtr也是使用引用计数进行对象管理。

TC_SharedPtr中有两个数据成员:

        T *m_px;
        detail::tc_shared_count_base *m_pn;

m_px是指向所管理的对象的指针,m_pn是一个管理结构。管理结构是继承自detail::tc_shared_count_base的tc_shared_count_impl_p或者tc_shared_count_impl_pd。下面对这三个类进行介绍

tc_shared_count_base

这个类就维护了一个数据成员:

tars::TC_Atomic m_use_count;

它是一个计数器。该类对外提供了dispose()接口供子类修改使用。dispose()是资源析构的核心函数。这也是tc_shared_count_impl_p和tc_shared_count_impl_pd不一样的地方。

tc_shared_count_impl_p

作为管理结构,除了继承自tc_shared_count_base的计数器,tc_shared_count_impl_p还保存了所管理的对象指针:

T *m_px;

它的任务就是实现dispose接口:

virtual void dispose()
{
    tc_checked_delete(m_px);
}

亦即简单将管理对象析构掉。

tc_shared_count_impl_pd

tc_shared_count_impl_pd和tc_shared_count_impl_p不同的是它为对象提供了一个删除器:

D m_deleter;

相应地,它通过调用删除器对对象进行处理:

virtual void dispose()  // no throw
{
     m_deleter(m_px);
}

用户可以通过向tc_shared_count_impl_pd注入自定义的删除器来控制对象的析构行为,比如默认删除器直接调用delete ptr,这不适用于数组。因此,如果是针对数组,则需要自定义删除器,调用delete [] ptr。

TC_EnableSharedFromThis

TC_SharedPtr要求所有需要使用该智能指针的类都继承TC_EnableSharedFromThis。这使得我们可以通过以下方式获得给定的一个对象指针的TC_SharedPtr:

T* ptr = new T;
TC_SharedPtr<T> tmp1 =  ptr;
TC_SharedPtr<T> tmp2 = ptr->sharedFromThis();

当然只有为原生指针构建了智能指针才能调用它的sharedFromThis()函数。这样就实现了共享对象的控制权。

构造函数

TC_SharedPtr提供了四个构造函数:

TC_SharedPtr();
explicit TC_SharedPtr(U *p)
TC_SharedPtr(U *p, D d)
TC_SharedPtr(const TC_SharedPtr& rhs)

第二个构造函数相对第一个构造函数多了一个d参数,这个参数标识的是前面说的删除器,用户可以通过这里给TC_SharedPtr注入删除器。这两个构造函数里还会有一行代码:

detail::tc_sp_enable_shared_from_this(this, p);

这主要是初始化指针p指向的对象的父类TC_EnableSharedFromThis中的数据成员,方便后面可以通过
p->sharedFromThis()获取该对象指针的TC_SharedPtr。

第四个构造函数会增加引用计数。

赋值重载

赋值重载是使用swap函数实现的,正如前面介绍第一个智能指针所说,赋值涉及到两个引用计数的变化。

TC_SharedPtr& operator=(const TC_SharedPtr& rhs)
{
     TC_SharedPtr(rhs).swap(*this);//TC_SharedPtr(rhs)构建一个局部变量
     return *this;
}

这里会为rhs构建一个临时TC_SharedPtr变量,因此rhs在构造函数中计数器会加1,然后调用swap函数,交换两者的计数器和对象指针,swap函数之后,等号左边的TC_SharedPtr保存的将是rhs对应的信息。swap执行完成之后,临时对象中存储的就是this指针指向的TC_SharedPtr,重载函数退出时,会调用该TC_SharedPtr的析构函数,计数器减1。因此一条语句就实现了两个计数器的变化。

总结

本文介绍了tars中实现的三种智能指针。TC_ScopedPtr比较简单,独占对象的控制权。TC_AutoPtr和TC_SharedPtr则使用引用计数控制对象的析构时机,它们都支持多个智能指针控制一个对象的生命周期。

### 部署 UI-TARS-7B-DPO 模型的方法 部署 UI-TARS-7B-DPO 模型通常涉及几个关键步骤,包括环境准备、模型加载以及服务启动。以下是详细的说明: #### 1. 环境准备 在开始之前,确保安装了必要的依赖项和库。推荐使用 Python 和 PyTorch 来处理深度学习模型。 ```bash pip install torch transformers accelerate ``` 如果计划在 GPU 上运行该模型,则需确认已正确安装 CUDA 工具包并验证其兼容性[^2]。 #### 2. 下载预训练模型 UI-TARS-7B-DPO 是基于 Hugging Face 的 Transformers 库发布的模型之一。可以通过以下命令下载模型权重文件: ```python from transformers import AutoModelForCausalLM, AutoTokenizer model_name_or_path = "huggingface/ui-tars-7b-dpo" tokenizer = AutoTokenizer.from_pretrained(model_name_or_path) model = AutoModelForCausalLM.from_pretrained(model_name_or_path) ``` 上述代码片段会自动从 Hugging Face Model Hub 中拉取 `ui-tars-7b-dpo` 模型及其对应的分词器[^3]。 #### 3. 加载模型到内存 对于较大的模型(如 7B 参数量),可能需要优化资源分配策略以减少显存占用。可以利用混合精度加速技术来提升效率: ```python import torch device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) # 使用 FP16 半精度模式降低显存消耗 if device.type == "cuda": model.half() ``` 此部分通过检测设备类型动态调整计算逻辑,并启用半精度浮点数支持以便更高效地管理硬件资源[^4]。 #### 4. 构建推理接口 为了让其他应用程序能够调用该模型,可创建 RESTful API 或 gRPC 接口作为对外暴露的服务端点。下面是一个简单的 FastAPI 实现案例: ```python from fastapi import FastAPI from pydantic import BaseModel from typing import List app = FastAPI() class InputData(BaseModel): prompt: str max_length: int = 50 @app.post("/generate/") def generate_text(input_data: InputData): inputs = tokenizer.encode( input_data.prompt, return_tensors="pt", truncation=True, padding=True ).to(device) outputs = model.generate(inputs, max_length=input_data.max_length) result = tokenizer.decode(outputs[0], skip_special_tokens=True) return {"generated_text": result} ``` 这段脚本定义了一个 POST 请求路径 `/generate/` ,允许客户端发送输入提示串及最大生成长度参数给服务器完成文本生成功能[^5]。 #### 5. 启动服务 最后一步就是将整个项目打包成 Docker 容器或者直接本地运行 Flask/FastAPI 应用来监听 HTTP 连接请求: ```bash uvicorn main:app --host 0.0.0.0 --port 8000 ``` 至此,已完成 UI-TARS-7B-DPO 模型的基本部署流程。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值