C++ 智能指针unique_ptr、shared_ptr、weak_ptr详解(包含代码示例)

一、std::unique_ptr详解

std::unique_ptr是C++11标准引入的智能指针,它是一个独占所有权的智能指针,这意味着同一时间只能有一个unique_ptr拥有某个对象的所有权。它在对象生命周期管理上提供了安全和高效的方式,避免了内存泄漏和未定义行为。

1. std::unique_ptr的详细介绍

  • 定义和初始化:

    std::unique_ptr<int> ptr1(new int(5)); // 使用裸指针初始化
    std::unique_ptr<int> ptr2 = std::make_unique<int>(10); // 使用std::make_unique初始化
    
  • 移动语义:
    std::unique_ptr不允许复制,但可以移动。这意味着可以将所有权从一个unique_ptr转移到另一个。

    std::unique_ptr<int> ptr3 = std::move(ptr1); // ptr1的所有权转移给ptr3
    
  • 释放资源:
    std::unique_ptr被销毁时,它会自动释放所拥有的资源。

    ptr2.reset(); // 手动释放资源
    

2. 优点和缺点

优点:

  1. 自动资源管理: 避免了手动释放内存,防止内存泄漏。
  2. 独占所有权: 确保一个对象同时只有一个所有者,避免悬挂指针。
  3. 效率高: 没有额外的引用计数开销。

缺点:

  1. 不支持复制: 只能通过移动语义转移所有权,不能复制unique_ptr
  2. 需要注意循环依赖: 如果两个对象互相持有std::unique_ptr,可能会导致无法正确释放资源。

3. 适用场景

  1. 独占资源管理: 适用于资源需要严格独占所有权的场景,如文件句柄、数据库连接等。
  2. 工厂函数返回值: 可以用来返回动态分配的对象而不担心内存泄漏。
  3. 容器中的元素: std::unique_ptr可以安全地存放在标准容器中,如std::vector

4. 代码示例及解释

#include <iostream>
#include <memory>
#include <vector>

// 一个简单的类,用于演示
class MyClass 
{
public:
    MyClass(int value) : value(value) 
    {
        std::cout << "Constructor: " << value << std::endl;
    }
    ~MyClass() 
    {
        std::cout << "Destructor: " << value << std::endl;
    }
    void show() const 
    {
        std::cout << "Value: " << value << std::endl;
    }
private:
    int value;
};

int main() 
{
    // 使用std::unique_ptr管理动态分配的MyClass对象
    std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>(1);
    ptr1->show();
    
    // 转移所有权
    std::unique_ptr<MyClass> ptr2 = std::move(ptr1);
    if (!ptr1) 
    {
        std::cout << "ptr1 is now null." << std::endl;
    }
    ptr2->show();
    
    // std::unique_ptr在容器中的使用
    std::vector<std::unique_ptr<MyClass>> vec;
    vec.push_back(std::make_unique<MyClass>(2));
    vec.push_back(std::make_unique<MyClass>(3));
    
    for (const auto& ptr : vec) 
    {
        ptr->show();
    }

    // 手动释放资源
    vec.clear();
    std::cout << "Vector cleared." << std::endl;

    return 0;
}

代码运行结果

Constructor: 1
Value: 1
ptr1 is now null.
Value: 1
Constructor: 2
Constructor: 3
Value: 2
Value: 3
Vector cleared.
Destructor: 3
Destructor: 2
Destructor: 1

代码解释

  1. 创建和使用std::unique_ptr:

    std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>(1);
    

    创建一个MyClass对象,使用ptr1管理其生命周期,并输出ptr1的值。

  2. 转移所有权:

    std::unique_ptr<MyClass> ptr2 = std::move(ptr1);
    

    ptr1的所有权转移给ptr2,并检查ptr1是否为空。

  3. std::unique_ptr在容器中的使用:

    std::vector<std::unique_ptr<MyClass>> vec;
    vec.push_back(std::make_unique<MyClass>(2));
    vec.push_back(std::make_unique<MyClass>(3));
    

    将多个MyClass对象的所有权存放在一个std::vector中,并输出每个对象的值。

  4. 手动释放资源:

    vec.clear();
    

    清空容器,自动调用析构函数释放资源。

通过这个示例,可以看到std::unique_ptr如何简化内存管理并确保资源的正确释放。

二、std::shared_ptr详解

std::shared_ptr是C++11标准引入的另一种智能指针,与std::unique_ptr不同,std::shared_ptr允许多个智能指针共享同一个对象。当最后一个std::shared_ptr被销毁时,所管理的对象才会被释放。std::shared_ptr使用引用计数来跟踪有多少智能指针指向同一对象。

1. std::shared_ptr的详细介绍

  • 定义和初始化:

    std::shared_ptr<int> ptr1(new int(5)); // 使用裸指针初始化
    std::shared_ptr<int> ptr2 = std::make_shared<int>(10); // 使用std::make_shared初始化
    
  • 引用计数:
    std::shared_ptr内部维护一个引用计数,记录指向同一对象的shared_ptr实例的数量。

    std::shared_ptr<int> ptr3 = ptr2; // 引用计数增加
    
  • 释放资源:
    当引用计数变为0时,std::shared_ptr自动释放所管理的对象。

    ptr2.reset(); // ptr2不再指向对象,但对象不被释放,因为ptr3仍指向它
    

2. 优点和缺点

优点:

  1. 自动资源管理: 避免手动管理内存,防止内存泄漏。
  2. 共享所有权: 允许多个指针共享同一个对象,适用于需要共享所有权的场景。
  3. 安全: 引用计数机制确保对象在不再需要时自动释放,防止悬挂指针。

缺点:

  1. 性能开销: 引用计数增加了内存和时间开销,尤其是在多线程环境中需要原子操作。
  2. 循环引用问题: 两个对象互相持有std::shared_ptr可能导致内存泄漏。

3. 适用场景

  1. 共享所有权的资源管理: 适用于需要在多个地方共享同一资源的场景,如观察者模式、图结构等。
  2. 长生命周期对象: 当对象需要在多个模块中使用且生命周期难以预测时。
  3. 需要动态管理对象生命周期: 如动态插件系统中需要管理的对象。

4. 代码示例及解释

#include <iostream>
#include <memory>
#include <vector>

// 一个简单的类,用于演示
class MyClass 
{
public:
    MyClass(int value) : value(value) 
    {
        std::cout << "Constructor: " << value << std::endl;
    }
    ~MyClass() 
    {
        std::cout << "Destructor: " << value << std::endl;
    }
    void show() const 
    {
        std::cout << "Value: " << value << std::endl;
    }
private:
    int value;
};

void use_shared_ptr(std::shared_ptr<MyClass> ptr) 
{
    std::cout << "Inside function: ";
    ptr->show();
}

int main() 
{
    // 使用std::shared_ptr管理动态分配的MyClass对象
    std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(1);
    ptr1->show();

    // 共享所有权
    std::shared_ptr<MyClass> ptr2 = ptr1;
    if (ptr1 && ptr2) 
    {
        std::cout << "Both ptr1 and ptr2 are valid.\n";
    }

    // 引用计数
    std::cout << "ptr1 use count: " << ptr1.use_count() << std::endl;
    std::cout << "ptr2 use count: " << ptr2.use_count() << std::endl;

    // 传递shared_ptr给函数
    use_shared_ptr(ptr2);

    // std::shared_ptr在容器中的使用
    std::vector<std::shared_ptr<MyClass>> vec;
    vec.push_back(std::make_shared<MyClass>(2));
    vec.push_back(std::make_shared<MyClass>(3));

    for (const auto& ptr : vec) 
    {
        ptr->show();
    }

    // 手动释放资源
    ptr1.reset();
    std::cout << "ptr1 reset, ptr2 use count: " << ptr2.use_count() << std::endl;

    return 0;
}

代码运行结果

Constructor: 1
Value: 1
Both ptr1 and ptr2 are valid.
ptr1 use count: 2
ptr2 use count: 2
Inside function: Value: 1
Constructor: 2
Constructor: 3
Value: 2
Value: 3
ptr1 reset, ptr2 use count: 1
Destructor: 3
Destructor: 2
Destructor: 1

代码解释

  1. 创建和使用std::shared_ptr:

    std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(1);
    

    创建一个MyClass对象,使用ptr1管理其生命周期,并输出ptr1的值。

  2. 共享所有权:

    std::shared_ptr<MyClass> ptr2 = ptr1;
    

    ptr1ptr2共享同一个MyClass对象,并输出引用计数。

  3. 传递std::shared_ptr给函数:

    use_shared_ptr(ptr2);
    

    ptr2传递给函数,函数内使用该指针访问对象。

  4. std::shared_ptr在容器中的使用:

    std::vector<std::shared_ptr<MyClass>> vec;
    vec.push_back(std::make_shared<MyClass>(2));
    vec.push_back(std::make_shared<MyClass>(3));
    

    将多个MyClass对象的所有权存放在一个std::vector中,并输出每个对象的值。

  5. 手动释放资源:

    ptr1.reset();
    

    重置ptr1,但对象不被释放,因为ptr2仍然持有该对象的所有权。

通过这个示例,可以看到std::shared_ptr如何简化内存管理、支持共享所有权并确保资源的正确释放。

三、std::weak_ptr详解

std::weak_ptr是C++11标准引入的智能指针,专门用于解决std::shared_ptr的循环引用问题。它不增加引用计数,而是提供了一种弱引用,用于观察和访问std::shared_ptr所管理的对象,而不会影响其生命周期。

1. std::weak_ptr的详细介绍

  • 定义和初始化:

    std::shared_ptr<int> sp = std::make_shared<int>(10);
    std::weak_ptr<int> wp = sp; // 使用shared_ptr初始化weak_ptr
    
  • 访问对象:
    std::weak_ptr不能直接访问对象,需要通过std::shared_ptr来访问。

    if (auto locked = wp.lock()) 
    { // 返回shared_ptr,如果对象存在
        std::cout << *locked << std::endl;
    } 
    else 
    {
        std::cout << "Object no longer exists." << std::endl;
    }
    
  • 检查有效性:
    可以使用expired()检查对象是否被销毁。

    if (wp.expired()) 
    {
        std::cout << "Object no longer exists." << std::endl;
    }
    

2. 优点和缺点

优点:

  1. 解决循环引用问题: 避免std::shared_ptr之间的循环引用导致内存泄漏。
  2. 轻量级: 不增加引用计数,对性能影响小。
  3. 安全访问: 通过lock()方法安全地访问对象。

缺点:

  1. 不能直接访问对象: 需要通过std::shared_ptr来访问,这增加了使用的复杂性。
  2. 检查有效性: 在访问前需要显式检查对象是否有效,稍显繁琐。

3. 适用场景

  1. 观察者模式: 适用于需要观测但不影响对象生命周期的场景。
  2. 避免循环引用: 如图结构、父子关系树等,防止对象间的循环引用。
  3. 缓存机制: 用于实现对象缓存,允许缓存项被外部持有而不延长其生命周期。

4. 代码示例及解释

#include <iostream>
#include <memory>

// 一个简单的类,用于演示
class MyClass 
{
public:
    MyClass(int value) : value(value) 
    {
        std::cout << "Constructor: " << value << std::endl;
    }
    ~MyClass() 
    {
        std::cout << "Destructor: " << value << std::endl;
    }
    void show() const 
    {
        std::cout << "Value: " << value << std::endl;
    }
private:
    int value;
};

void check_weak_ptr(const std::weak_ptr<MyClass>& wp) 
{
    if (auto sp = wp.lock()) 
    {
        std::cout << "Object is alive: ";
        sp->show();
    } 
    else 
    {
        std::cout << "Object no longer exists." << std::endl;
    }
}

int main() 
{
    std::shared_ptr<MyClass> sp1 = std::make_shared<MyClass>(1);
    std::weak_ptr<MyClass> wp1 = sp1;

    // 访问对象
    check_weak_ptr(wp1);

    // 共享所有权
    std::shared_ptr<MyClass> sp2 = sp1;
    std::cout << "sp1 use count: " << sp1.use_count() << std::endl;

    // 重置shared_ptr,weak_ptr不影响对象的生命周期
    sp1.reset();
    std::cout << "sp1 reset, sp2 use count: " << sp2.use_count() << std::endl;

    // 访问对象
    check_weak_ptr(wp1);

    // 释放所有shared_ptr
    sp2.reset();
    std::cout << "sp2 reset." << std::endl;

    // 访问对象
    check_weak_ptr(wp1);

    return 0;
}

代码运行结果

Constructor: 1
Object is alive: Value: 1
sp1 use count: 2
sp1 reset, sp2 use count: 1
Object is alive: Value: 1
sp2 reset.
Object no longer exists.
Destructor: 1

代码解释

  1. 创建和使用std::shared_ptrstd::weak_ptr:

    std::shared_ptr<MyClass> sp1 = std::make_shared<MyClass>(1);
    std::weak_ptr<MyClass> wp1 = sp1;
    

    创建一个MyClass对象,并使用shared_ptr管理其生命周期,同时创建一个weak_ptr观察该对象。

  2. 访问对象:

    check_weak_ptr(wp1);
    

    调用check_weak_ptr函数,通过weak_ptrlock()方法访问对象,如果对象存在则输出其值。

  3. 共享所有权:

    std::shared_ptr<MyClass> sp2 = sp1;
    

    创建另一个shared_ptr共享同一个对象,并输出引用计数。

  4. 重置shared_ptr:

    sp1.reset();
    

    重置sp1sp2仍然持有对象的所有权,因此对象不会被释放。

  5. 访问对象:

    check_weak_ptr(wp1);
    

    再次调用check_weak_ptr函数,通过weak_ptr访问对象。

  6. 释放所有shared_ptr:

    sp2.reset();
    

    重置sp2,此时没有shared_ptr持有对象,对象被销毁。

  7. 访问对象:

    check_weak_ptr(wp1);
    

    最后一次调用check_weak_ptr函数,weak_ptr不能再访问对象,输出对象不存在。

通过这个示例,可以看到std::weak_ptr如何用于安全地观察和访问std::shared_ptr所管理的对象,避免循环引用并确保资源正确释放。

  • 46
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
C++中,`unique_ptr`、`shared_ptr`、`weak_ptr`是三种常用的智能指针,用于管理动态分配的内存,避免内存泄漏和悬空指针等问题。 1. `unique_ptr`:是一种独占式智能指针,表示一个对象的所有权只能被一个`unique_ptr`持有,不能被其他指针或引用所共享。当`unique_ptr`超出作用域或被显式释放时,它所指向的对象将被自动销毁。例如: ```cpp std::unique_ptr<int> p(new int(10)); ``` 2. `shared_ptr`:是一种共享式智能指针,表示一个对象的所有权可以被多个`shared_ptr`共享。每个`shared_ptr`维护一个引用计数器,当引用计数器变为0时,它所指向的对象将被自动销毁。`shared_ptr`还支持自定义删除器,可以在对象销毁时执行特定的操作。例如: ```cpp std::shared_ptr<int> p = std::make_shared<int>(10); std::shared_ptr<int> q = p; ``` 3. `weak_ptr`:是一种弱引用智能指针,不能直接访问所指向的对象,只能通过调用`lock()`方法获得一个指向所指对象的`shared_ptr`。当`weak_ptr`所指向的对象已经被销毁时,`lock()`方法将返回一个空的`shared_ptr`。`weak_ptr`主要用于解决`shared_ptr`的循环引用问题,避免内存泄漏。例如: ```cpp std::shared_ptr<int> p = std::make_shared<int>(10); std::weak_ptr<int> q = p; std::shared_ptr<int> r = q.lock(); ``` 这些智能指针都定义在`<memory>`头文件中,并且都是模板类,可以用于管理各种类型的动态分配内存。在实际开发中,应尽量使用智能指针来管理内存,避免手动管理内存所带来的麻烦和风险。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Warren++

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值