有效运用auto_ptr

本文详细介绍了 C++ 中的标准 auto_ptr 智能指针的使用方法,包括如何利用其自动资源管理特性编写健壮的代码,并讨论了常见误用及潜在的 bug 风险。
作者:佚名 文章来源:不详 点击数: 更新时间:2005-10-6

翻译:elmar

本文发表于1999年10月份的C/C++ Users Journal, 17(10)

很多人听说过标准auto_ptr智能指针机制,但并不是每个人都天天使用它。这真是个遗憾,因为auto_ptr优雅地解决了C++设计和编码中常见的问题,正确地使用它可以生成健壮的代码。本文阐述了如何正确运用auto_ptr来让你的代码更加安全——以及如何避免对auto_ptr危险但常见的误用,这些误用会引发间断性发作、难以诊断的bug。

1.为什么称它为“自动”指针?
auto_ptr只是众多可能的智能指针之一。许多商业库提供了更复杂的智能指针,用途广泛而令人惊异,从管理引用的数量到提供先进的代理服务。可以把标准C++ auto_ptr看作智能指针的Ford Escort(elmar注:可能指福特的一种适合家居的车型):一个简易、通用的智能指针,它不包含所有的小技巧,不像专用的或高性能的智能指针那么奢华,但是它可以很好的完成许多普遍的工作,它很适合日常性的使用。

auto_ptr所做的事情,就是动态分配对象以及当对象不再需要时自动执行清理。这里是一个简单的代码示例,没有使用auto_ptr所以不安全
// 示例 1(a): 原始代码
//
void f()
{
T* pt( new T );

/*...更多的代码...*/

delete pt;
}

我们大多数人每天写类似的代码。如果f()函数只有三行并且不会有任何意外,这么做可能挺好的。但是如果f()从不执行delete语句,或者是由于过早的返回,或者是由于执行函数体时抛出了异常,那么这个被分配的对象就没有被删除,从而我们产生了一个经典的内存泄漏。

能让示例1(a)安全的简单办法是把指针封装在一个“智能的”类似于指针的对象里,这个对象拥有这个指针并且能在析构时自动删除这个指针所指的对象。因为这个智能指针可以简单的当成一个自动的对象(这就是说,它出了作用域时会自动毁灭),所以很自然的把它称之为“智能”指针:

// 示例 1(b): 安全代码, 使用了auto_ptr
//
void f()
{
auto_ptr pt( new T );

/*...更多的代码...*/

} // 酷: 当pt出了作用域时析构函数被调用,
// 从而对象被自动删除

现在代码不会泄漏T类型的对象,不管这个函数是正常退出还是抛出了异常,因为pt的析构函数总是会在出栈时被调用。清理会自动进行。

最后,使用一个auto_ptr就像使用一个内建的指针一样容易,而且如果想要“撤销”资源,重新采用手动的所有权,我们只要调用release():

// 示例 2: 使用一个 auto_ptr
//
void g()
{
T* pt1 = new T;
// 现在,我们有了一个分配好的对象

// 将所有权传给了一个auto_ptr对象
auto_ptr pt2( pt1 );

// 使用auto_ptr就像我们以前使用简单指针一样
*pt2 = 12; // 就像 "*pt1 = 12;"
pt2->SomeFunc(); // 就像 "pt1->SomeFunc();"

// 用get()来获得指针的值
assert( pt1 == pt2.get() );

// 用release()来撤销所有权
T* pt3 = pt2.release();

// 自己删除这个对象,因为现在
// 没有任何auto_ptr拥有这个对象
delete pt3;

} // pt2不再拥有任何指针,所以不要
// 试图删除它...ok,不要重复删除

最后,我们可以使用auto_ptr的reset()函数来重置auto_ptr使之拥有另一个对象。如果这个auto_ptr已经拥有了一个对象,那么,它会先删除已经拥有的对象,因此调用reset()就如同销毁这个auto_ptr,然后新建一个并拥有一个新对象:

// 示例 3: 使用reset()
//
void h()
{
auto_ptr pt( new T(1) );

pt.reset( new T(2) );
// 删除由"new T(1)"分配出来的第一个T

} // 最后,pt出了作用域,
// 第二个T也被删除了

#pragma once #include<functional> namespace dfq { //给什么类型就形成什么指针 //RAII利用对象的生命周期来控制资源的获取释放 template<class T> class auto_ptr { public: auto_ptr(T* ptr = nullptr) :_ptr(ptr) { } ~auto_ptr() { delete _ptr;//运用包装器,释放资源free\delete\delete[] } //拷贝构造 //sp1(sp2) auto_ptr(auto_ptr<T>& sp) :_ptr(sp._ptr) { sp._ptr = nullptr;//被拷贝指针悬空,不推荐 } //像指针一样 T* operator->() { return _ptr; } T& operator*() { return *_ptr; } private: T* _ptr; }; template<class T> class unique_ptr { public: unique_ptr(T* ptr = nullptr) :_ptr(ptr) { } ~unique_ptr() { delete _ptr;//运用包装器,释放资源free\delete\delete[] } //拷贝构造、赋值重载不执行,指针之间就不能赋值 //sp1(sp2) unique_ptr(unique_ptr<T>& sp) = delete; unique_ptr<T>& operator=(unique_ptr<T>& sp) = delete; //像指针一样 T* operator->() { return _ptr; } T& operator*() { return *_ptr; } private: T* _ptr; }; template<class T> class shared_ptr { public: shared_ptr(T* ptr = nullptr) :_ptr(ptr) ,_pcount(new int(1))//指针对象创建时,说明刚好申请一块资源 //此时只有一个指针指向该资源 {} //只穿一个参数,无法实例化出下面这个构造函数 template<class D> shared_ptr(T* ptr, D del = [](T* ptr) {delete ptr; }) :_ptr(ptr) , _pcount(new int(1)) ,_del(del)//定制删除器,传什么怎么删除 { } //拷贝构造,新增一个指针指向该资源,引用计数++ //sp1(sp2) shared_ptr(const shared_ptr<T>& sp) :_ptr(sp._ptr) ,_pcount(sp._pcount) { ++(*_pcount); } //赋值重载: //ap1 = ap2; shared_ptr<T>& operator=(shared_ptr<T>& sp) { //1.指向同一块资源的指针没必要赋值 if (_ptr == sp._ptr) return *this; //2.指向不同资源的指针间赋值需注意 //前一块资源的释放问题,后一资源引用计数++ if (--(*_pcount) == 0) { _del(_ptr); delete _pcount; _ptr = nullptr; _pcount = nullptr; } _ptr = sp._ptr; _pcount = sp._pcount; ++(*_pcount); } ~shared_ptr() { if (--(*_pcount) == 0) { _del(_ptr); delete _pcount; _ptr = nullptr; _pcount = nullptr; } } //像指针一样 T* operator->() { return _ptr; } T& operator*() { return *_ptr; } T* get() { return _ptr; } int use_count() { return *_pcount; } private: T* _ptr; int* _pcount;//引用计数,有多少个指针指向该资源 function<void(T*)> _del = [](T* ptr) {delete ptr; }; //释放资源的函数类型一致,且默认为delete }; template<class T> class weak_ptr { public: weak_ptr(T* ptr = nullptr) :_ptr(ptr) {} //sp1(sp2) weak_ptr(const shared_ptr<T>& sp) :_ptr(sp.get()) {} weak_ptr(const weak_ptr<T>& sp) :_ptr(sp._ptr) {} //ap1 = ap2; weak_ptr<T>& operator=(weak_ptr<T>& sp) { //1.指向同一块资源的指针没必要赋值 if (_ptr == sp._ptr) return *this; _ptr = sp._ptr; } weak_ptr<T>& operator=(shared_ptr<T>& sp) { //1.指向同一块资源的指针没必要赋值 if (_ptr == sp.get()) return *this; _ptr = sp.get(); } //像指针一样 T* operator->() { return _ptr; } T& operator*() { return *_ptr; } T* get() { return _ptr; } private: T* _ptr;//不参与资源管理,只是简单的访问 }; } 仔细评价这份实现,重点在于unique_ptr\shared_ptr\weak_ptr
09-19
<think> 我们正在分析用户提供的智能指针实现代码片段,并评价其优缺点及实现合理性。用户提供的代码只展示了weak_ptr的部分实现,但问题要求评价unique_ptr、shared_ptr和weak_ptr。由于用户只给出了weak_ptr的实现片段,我们只能针对这个片段进行评价,但我们可以根据标准库的实现原理来讨论这三个智能指针应有的实现要点,并指出给定weak_ptr实现的不足。 首先,回顾用户提供的weak_ptr实现代码: ```cpp template<class T> class weak_ptr { public: weak_ptr() {} weak_ptr(const shared_ptr<T>& sp) :_ptr(sp.get()) {} weak_ptr<T>& operator=(const shared_ptr<T>& sp) { _ptr = sp.get(); return *this; } private: T* _ptr = nullptr; }; ``` 这个实现非常简化,它只包含一个裸指针`_ptr`。然而,标准库中的`weak_ptr`需要与`shared_ptr`共享控制块,并且能够安全地检测所指向的对象是否还存在(即通过`expired()`函数)。该实现存在以下问题: 1. 没有使用控制块(control block):标准库的`shared_ptr`和`weak_ptr`使用一个控制块来管理引用计数(包括强引用计数和弱引用计数)。当最后一个`shared_ptr`被销毁时,对象会被销毁,但控制块并不会立即释放,因为`weak_ptr`还需要使用控制块来判断对象是否已经销毁。只有当最后一个`weak_ptr`也被销毁时,控制块才会被释放。 2. 当前的`weak_ptr`实现仅保存了裸指针,无法检测指针的有效性。也就是说,当`shared_ptr`释放了资源后,`weak_ptr`中的裸指针就变成了悬垂指针,无法安全地使用。 3. 缺少必要的接口:例如`lock()`函数,它应该返回一个`shared_ptr`,如果对象还存在就指向它,否则返回空的`shared_ptr`。此外,也没有实现`expired()`函数。 4. 拷贝构造函数和赋值运算符重载只针对`shared_ptr`,而`weak_ptr`还应该支持从另一个`weak_ptr`构造和赋值。同时,赋值给`weak_ptr`时,应该考虑原来指向的控制块引用计数(弱引用)的递减,以及新控制块弱引用的递增(如果有)。 5. 没有考虑多线程安全:标准库的智能指针通常要求引用计数的操作是原子操作。 因此,这个`weak_ptr`的实现是不完整的,不能提供`weak_ptr`应有的功能。 现在,我们扩展到讨论unique_ptr、shared_ptr和weak_ptr的标准实现应该具备的要素: ### unique_ptr 应该具有独占所有权的语义,只允许移动,不允许复制。通常只需要一个指针成员,在析构时释放资源。需要实现移动构造和移动赋值,并支持自定义删除器。该实现相对简单,但要求高效且无额外开销。 ### shared_ptr 需要维护一个控制块,其中包含: - 指向被管理对象的指针 - 强引用计数(shared count) - 弱引用计数(weak count) - 可选的自定义删除器和分配器 当强引用计数变为0时,释放所管理的对象(调用析构函数和释放内存)。当弱引用计数也变为0时,释放控制块本身。 需要实现线程安全的引用计数操作(通常使用原子操作)。 另外,shared_ptr需要支持拷贝构造和拷贝赋值,这些操作会增加强引用计数。 ### weak_ptr 必须与shared_ptr使用相同的控制块。它不拥有对象的所有权,因此不会增加强引用计数,但会增加弱引用计数。主要提供`lock()`函数来获得一个shared_ptr(增加强引用计数,如果对象还存在的话),以及`expired()`函数检查对象是否已被释放。 根据上述分析,用户提供的weak_ptr实现过于简单,无法满足基本要求。 下面,我们针对用户提供的weak_ptr代码进行优缺点总结: 优点: - 实现简单,易于理解。 - 能够从shared_ptr构造,并获取其内部指针。 缺点: - 无法检测所指向的对象是否已经被释放(没有使用控制块,无法通过引用计数判断)。 - 缺少关键接口(lock, expired)。 - 没有实现弱引用计数,因此无法与控制块的生命周期关联。 - 当shared_ptr释放资源后,weak_ptr中的指针成为悬垂指针,使用它会导致未定义行为。 - 没有考虑线程安全。 因此,这个weak_ptr实现是不合理的,不能用于实际场景。 建议:完整的weak_ptr实现应该包含一个指向控制块的指针(而不是直接持有对象指针),并且控制块中需要管理弱引用计数。在构造和赋值时,需要正确地增加和减少弱引用计数。 由于用户没有提供unique_ptr和shared_ptr的实现,我们无法评价,但我们可以推测:如果shared_ptr没有实现控制块,那么weak_ptr也无法正确实现。 最后,我们生成相关问题: </think> ### 对智能指针实现代码的评价 以下是针对用户提供的 `weak_ptr` 实现片段的分析,结合标准库中 `unique_ptr`、`shared_ptr` 和 `weak_ptr` 的典型实现原理进行评价: --- #### 1. **`weak_ptr` 实现分析** ```cpp template<class T> class weak_ptr { public: weak_ptr() {} weak_ptr(const shared_ptr<T>& sp) :_ptr(sp.get()) {} // ✘ weak_ptr<T>& operator=(const shared_ptr<T>& sp) { // ✘ _ptr = sp.get(); return *this; } private: T* _ptr = nullptr; // ✘ }; ``` **缺点与问题:** 1. **缺少控制块关联** - 标准 `weak_ptr` 需关联 `shared_ptr` 的控制块(含强弱引用计数),而非直接存储裸指针 `_ptr`[^2]。 - ✅ 正确实现应存储指向控制块的指针(如 `ControlBlock* ctrl`),通过 `ctrl->weak_count` 管理弱引用。 2. **未实现 `lock()` 功能** - `lock()` 是 `weak_ptr` 的核心接口,用于安全提升为 `shared_ptr`: ```cpp shared_ptr<T> lock() const { return expired() ? nullptr : shared_ptr<T>(*this); } ``` - 当前实现无法检测对象是否已被释放(缺少 `expired()`)。 3. **线程安全性缺失** - 引用计数修改需原子操作(如 `std::atomic`),当前实现非线程安全。 4. **析构函数未减少弱引用计数** - `weak_ptr` 析构时需减少控制块的 `weak_count`,并在计数归零时销毁控制块[^2]。 --- #### 2. **`shared_ptr` 实现要点(对比参考)** 理想实现需包含: - **控制块结构**: ```cpp struct ControlBlock { T* ptr; // 托管对象指针 std::atomic<size_t> ref_count; // 强引用计数 std::atomic<size_t> weak_count; // 弱引用计数 }; ``` - **`enable_shared_from_this` 支持** 通过 `weak_ptr` 成员实现 `shared_from_this()`,确保对象内部获取安全的 `shared_ptr`[^2]。 - **自定义删除器支持**:需在控制块中存储删除逻辑。 --- #### 3. **`unique_ptr` 实现要点** 典型实现特点: - **独占所有权**:禁用拷贝构造/赋值,仅支持移动语义。 - **零开销**:无额外内存开销(对比 `shared_ptr` 的控制块)。 - **自定义删除器**:通过模板参数支持(如 `unique_ptr<T, Deleter>`)。 --- ### **总结评价** | 智能指针 | 用户实现问题 | 合理实现要求 | |------------|---------------------------------------|----------------------------------| | `weak_ptr` | ✘ 无控制块关联<br>✘ 缺少 `lock()`<br>✘ 非线程安全 | 关联控制块、原子计数、`lock()` 接口 | | `shared_ptr`| 未提供实现 | 控制块管理、原子操作、环形引用检测 | | `unique_ptr`| 未提供实现 | 移动语义、禁止拷贝、零运行时开销 | **改进方向**: 1. 为 `weak_ptr` 添加控制块指针成员(代替裸指针 `_ptr`)。 2. 实现 `lock()` 和 `expired()` 接口。 3. 引用计数改用 `std::atomic<size_t>`。 4. 补充 `shared_ptr` 和 `unique_ptr` 的完整实现(含析构逻辑)。 --- ### 相关问题 1. 如何实现 `shared_ptr` 的线程安全引用计数? 2. `weak_ptr` 的 `lock()` 方法在哪些场景下是必要的? 3. `unique_ptr` 如何通过移动语义避免资源所有权问题? 4. 智能指针的控制块内存管理有哪些优化策略? [^1]: 引用自用户提供的 `weak_ptr` 实现片段 [^2]: 关联控制块和 `enable_shared_from_this` 的实现原理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值