智能指针全指南

unique_ptr

unique_ptr是C++中一种智能指针,它代表对某资源的唯一所有权,确保资源在其生命周期结束时被自动删除,有效防止内存泄漏。

特点:

独占所有权unique_ptr==确保同一时间内只有一个智能指针拥有对资源的所有权。==这意味着你不能复制(copy)一个unique_ptr,企图让两个unique_ptr共享同一个对象,这有助于避免资源的多重释放问题。

自动资源管理:当unique_ptr离开作用域时,它会自动删除(通过调用delete)所拥有的对象,从而防止内存泄漏。这种机制遵循RAII(Resource Acquisition Is Initialization)原则。

可移动性:虽然unique_ptr不能被复制,但它可以被移动(move)。移动操作转移资源的所有权,使得资源能够有效地在函数间或者数据结构间传递,而不需要显式地释放和重新分配内存。

禁止拷贝,允许移动:通过禁用拷贝构造函数和赋值运算符,unique_ptr强制执行独占所有权的原则,但提供了移动构造函数和移动赋值运算符,允许资源的所有权安全地转移。

综上所述,unique_ptr设计的核心目标是提供一种简单而安全的独占资源所有权管理方式,特别适用于那些需要明确资源拥有者并且避免资源共享的情景。

以下是使用unique_ptr的基本步骤和一些典型操作:

1. 包含头文件

要使用unique_ptr,首先需要包含<memory>头文件。

#include <memory>

2. 创建unique_ptr

创建unique_ptr有两种常见方式:直接构造和使用std::make_unique函数。

直接构造

std::unique_ptr<int> ptr(new int(42)); // 自C++11起

使用make_unique(推荐,自C++14起)

std::unique_ptr<int> ptr = std::make_unique<int>(42);

3. 访问和修改所指向的对象

直接通过箭头操作符(->)或解引用操作符(*)来访问和修改对象。

std::unique_ptr<int> ptr = std::make_unique<int>(42);

// 使用解引用运算符(*)来访问和修改值
std::cout << "*ptr 的值: " << *ptr << std::endl;
*ptr = 17; // 修改值
std::cout << "修改后 *ptr 的值: " << *ptr << std::endl;

// 或者使用箭头运算符(->)来调用成员函数或访问成员变量(如果是指向对象的指针)
// 假设是某个类的对象,比如一个拥有name成员变量的类Person
std::unique_ptr<Person> personPtr = std::make_unique<Person>("Alice");
std::cout << "personPtr 的名字: " << personPtr->getName() << std::endl;

// 注意:这里使用->是因为假设Person类有getName成员函数
// 实际上,对于int或其他基本类型,箭头运算符用于访问不存在的成员是没有意义的

注意:上例中的value应替换为实际成员变量名,这里仅为示例。

4. 转移所有权

由于unique_ptr不允许拷贝,但支持移动(转移所有权)。

std::unique_ptr<int> ptr1 = std::make_unique<int>(42);
std::unique_ptr<int> ptr2 = std::move(ptr1); // 移动所有权给ptr2
// 现在ptr1不再拥有任何资源

5. 释放资源

你可以使用reset()方法来释放当前unique_ptr所持有的资源,并可选择性地指向另一个新资源。

ptr.reset(new int(77)); // 释放原资源并重新分配
// 或
ptr.reset(); // 仅释放资源,不分配新资源

6. 获取原始指针

使用get()方法可以获取unique_ptr内部存储的原始指针,但需谨慎使用,确保不会导致资源泄露。

int* rawPtr = ptr.get();

7. 析构

unique_ptr离开作用域时,它会自动删除所拥有的对象,无需手动调用析构函数。

{
    std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>();
    // ...使用ptr
} // ptr在此处自动析构,MyClass实例被删除

小结

unique_ptr提供了一种安全且高效的内存管理方式,通过禁止拷贝和自动管理资源生命周期,有助于编写更加健壮和易于维护的C代码。掌握其基本用法对于现代C编程至关重要。

shared_ptr

std::shared_ptr是C++标准库提供的智能指针之一,它允许多个指针共享同一个对象的所有权,并且当最后一个指向该对象的shared_ptr销毁时,该对象会被自动删除,从而有效地管理动态分配的内存,防止内存泄漏。下面是如何使用std::shared_ptr的基本指南:

1. 引入头文件

首先,你需要包含<memory>头文件来使用std::shared_ptr

#include <memory>

2. 创建shared_ptr

  • 直接构造:通过new表达式和构造函数创建。

std::shared_ptr<int> ptr(new int(42));

  • 使用make_shared:推荐使用std::make_shared,因为它能优化内存分配,通常只有一个内存分配动作。

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

3. 共享所有权

多个shared_ptr可以指向相同的对象,共享其所有权。

std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
std::shared_ptr<int> ptr2 = ptr1; // ptr2共享ptr1指向的对象

4. 访问对象

通过解引用(*)或箭头(->)操作符访问对象。

*ptr = 100; // 修改值
std::cout << "Value: " << *ptr << std::endl; // 输出值

5. 释放资源

当所有指向对象的shared_ptr销毁或被重新赋值时,对象将被自动删除。

6. 引用计数

shared_ptr内部维护一个引用计数,用来追踪有多少个shared_ptr共享同一对象。可以通过非标准方法查看引用计数(虽然不建议直接操作),但这依赖于具体实现。

void shared_ptr_f()
{
    std::shared_ptr<int> sp1 = std::make_shared<int>(42);
    std::cout << "sp1's use count: " << sp1.use_count() << std::endl; // 输出应该是 1

    std::shared_ptr<int> sp2 = sp1;
    std::cout << "After sp2 = sp1, sp1's use count: " << sp1.use_count() <<             std::endl; // 输出应该是 2
    std::cout << "sp2's use count: " << sp2.use_count() << std::endl; // 输出也是 2,因为它们共享同一对象

    sp2.reset();
    std::cout << "After sp2.reset(), sp1's use count: " << sp1.use_count() << std::endl; // 输出应该是 1,因为sp2不再指向该对象
}

7. 使用自定义删除器

std::shared_ptr允许指定一个删除器函数或函数对象,用于定制化对象的删除方式。

void customDeleter(int* p) {
    delete p;
    std::cout << "Custom deletion!" << std::endl;
}

std::shared_ptr<int> ptrWithCustomDel(new int(42), customDeleter);

8. 转换为unique_ptr

虽然不常见,但可以通过转移所有权的方式将shared_ptr转换为unique_ptr

std::unique_ptr<int> uniquePtr = std::move(ptr);

注意事项

  • 循环引用:当shared_ptr形成循环引用时,可能导致对象无法被释放。这时应考虑使用std::weak_ptr来打破循环。
  • 性能shared_ptr相比原始指针或unique_ptr有额外的开销,因为它需要维护引用计数。

通过上述指南,您可以开始在项目中安全且有效地使用std::shared_ptr来管理动态分配的资源。

weak_ptr

std::weak_ptr是C++智能指针家族的一员,它是一种非拥有型的智能指针,主要用于解决由std::shared_ptr可能引发的循环引用问题。weak_ptr本身并不增加它所指向的对象的引用计数,因此,即使所有相关的shared_ptr销毁,通过weak_ptr仍然可以观测对象是否存在,而不会阻止对象的释放。

例子:

class Node final {
public:
    using this_type = Node;

    // 注意这里,别名改用weak_ptr
    using shared_type = std::weak_ptr<this_type>;

public:
    shared_type next; // 因为用了别名,所以代码不需要改动
};

auto n1 = std::make_shared<Node>(); // 工厂函数创建智能指针
auto n2 = std::make_shared<Node>(); // 工厂函数创建智能指针

n1->next = n2; // 两个节点互指,形成了循环引用
n2->next = n1;

assert(n1.use_count() == 1); // 因为使用了weak_ptr,引用计数为1
assert(n2.use_count() == 1); // 打破循环引用,不会导致内存泄漏

if (!n1->next.expired()) { // 检查指针是否有效
    auto ptr = n1->next.lock(); // lock()获取shared_ptr
    assert(ptr == n2);
}

PS:

expired() 方法是 std::weak_ptr 类的一个成员函数,它的作用是用来检测其所指向的对象是否已经被销毁。当 weak_ptr 指向的对象没有任何对应的 shared_ptr 引用(即引用计数为0),那么对象会被删除,此时 weak_ptr 就被认为是“过期的”(expired)。

具体来说,当你调用 expired() 函数时:

  • 如果 weak_ptr 指向的对象已经被释放,此函数返回 true,表示这个 weak_ptr 是无效的或“过期的”。
  • 如果对象仍然存在(即至少有一个 shared_ptr 指向它),函数返回 false,表示 weak_ptr 仍然是有效的。

这个方法非常重要,尤其是在处理可能形成循环引用的情景下,通过 weak_ptr 可以安全地观察对象是否存在,而又不增加对象的引用计数,从而避免了不必要的生命周期延长。当需要实际访问对象时,可以先用 expired() 检查对象是否还在,然后使用 lock() 方法尝试获取一个临时的 shared_ptr 来安全访问对象。如果对象已不存在(expired() 返回 true),则 lock() 会返回一个空的 shared_ptr

以下是使用std::weak_ptr的基本指南:

1. 引入头文件

std::shared_ptr一样,使用std::weak_ptr也需要包含<memory>头文件。

#include <memory>

2. 创建weak_ptr

通常,weak_ptr是从一个shared_ptr实例创建的,它不改变源shared_ptr的引用计数。

std::shared_ptr<int> shared(new int(42));
std::weak_ptr<int> weak = shared; // 创建一个weak_ptr,观察shared指向的对象

3. 检查对象有效性

由于weak_ptr不控制对象生命周期,访问对象之前必须检查对象是否还存在。

if(auto sharedLocked = weak.lock()) { // 尝试锁定对象
    // 对象仍然存在,现在可以安全使用sharedLocked
    std::cout << "Value: " << *sharedLocked << std::endl;
} else {
    // 对象已被销毁
    std::cout << "Object no longer exists." << std::endl;
}

4. 锁定对象(lock())

通过lock()方法,可以临时获得一个指向对象的shared_ptr,这会增加引用计数。此shared_ptr的生命周期结束后,引用计数会相应减少。

std::shared_ptr<int> tempSharedPtr = weak.lock(); // 锁定对象,获取shared_ptr

5. 更新weak_ptr

如果原始shared_ptr的内容改变了,可以通过再次从新的shared_ptr实例创建weak_ptr来更新它。

注意事项

  • 循环引用:当两个或更多的shared_ptr相互引用形成循环时,可能导致内存泄漏。使用weak_ptr来引用其中一个或多个对象,可以避免这种循环引用。
  • 不直接管理资源weak_ptr不能直接用于删除它所指向的对象,它只是观察者,只有通过lock()转换为shared_ptr后,才能访问和管理资源。
  • 定期检查:如果你的代码逻辑依赖于weak_ptr所观察的对象存在,应该定期检查对象的有效性,以避免对已释放资源的访问。

通过上述指南,您可以有效地利用std::weak_ptr来增强程序的内存管理机制,尤其是在处理复杂数据结构和避免循环引用方面。

自己实现一个智能指针

等待更新

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值