C++的智能指针

C++的智能指针

智能指针是C++中的一个强大特性,它能帮助我们更安全、更方便地管理动态分配的内存。让我们通过一些生动的比喻和例子来深入了解智能指针。

1. 为什么需要智能指针?

想象你在玩一个魔法世界的游戏。在这个世界里,你可以创造各种物品(对象),但如果不及时清理,这些物品就会堆积成垃圾,占据宝贵的空间。普通指针就像是没有魔法的普通人,他们可以创造物品,但经常忘记清理。而智能指针就像是具有自动清理能力的魔法师,它们不仅可以创造物品,还能在物品不再需要时自动清理。

2. 智能指针的类型

C++提供了三种主要的智能指针:

  1. std::unique_ptr:独占所有权的魔法师
  2. std::shared_ptr:共享所有权的魔法师
  3. std::weak_ptr:观察者魔法师

让我们逐一了解这些"魔法师"。

3. std::unique_ptr

unique_ptr 就像一个独占欲很强的魔法师。它创造的物品只能由它自己管理,不能被其他魔法师共享。

#include <memory>
#include <iostream>

class Treasure {
public:
    Treasure(std::string n) : name(n) {
        std::cout << "Treasure " << name << " created!" << std::endl;
    }
    ~Treasure() {
        std::cout << "Treasure " << name << " destroyed!" << std::endl;
    }
    std::string name;
};

int main() {
    {
        std::unique_ptr<Treasure> goldCoin = std::make_unique<Treasure>("Gold Coin");
        // 使用 goldCoin
        std::cout << "I have a " << goldCoin->name << std::endl;
    } // goldCoin 在这里自动销毁

    std::cout << "End of the spell" << std::endl;
    return 0;
}

输出:

Treasure Gold Coin created!
I have a Gold Coin
Treasure Gold Coin destroyed!
End of the spell

注意 goldCoin 是如何在离开作用域时自动被销毁的。这就是 unique_ptr 的魔力!

4. std::shared_ptr

shared_ptr 就像一群友好的魔法师,他们一起管理同一个物品。只有当所有魔法师都不再需要这个物品时,它才会被清理。

#include <memory>
#include <iostream>

class Treasure {
public:
    Treasure(std::string n) : name(n) {
        std::cout << "Treasure " << name << " created!" << std::endl;
    }
    ~Treasure() {
        std::cout << "Treasure " << name << " destroyed!" << std::endl;
    }
    std::string name;
};

int main() {
    std::shared_ptr<Treasure> sharedGem = std::make_shared<Treasure>("Magic Gem");
    
    {
        std::shared_ptr<Treasure> anotherGem = sharedGem;
        std::cout << "Gem is shared by " << sharedGem.use_count() << " wizards" << std::endl;
    }
    
    std::cout << "Now Gem is shared by " << sharedGem.use_count() << " wizard" << std::endl;
    
    return 0; // sharedGem 在这里被销毁
}

输出:

Treasure Magic Gem created!
Gem is shared by 2 wizards
Now Gem is shared by 1 wizard
Treasure Magic Gem destroyed!

看到了吗?Magic Gem 在被两个魔法师共享时并没有被销毁,只有当最后一个魔法师不再需要它时,它才被清理。

解析代码

在这个代码示例中,sharedGem 是一个 std::shared_ptr<Treasure>,它管理一个名为 “Magic Gem” 的 Treasure 对象。理解 shared_ptr 的生命周期和销毁时机,涉及到 C++ 中作用域和引用计数的概念。

代码解读

  1. 创建和初始化 shared_ptr

    std::shared_ptr<Treasure> sharedGem = std::make_shared<Treasure>("Magic Gem");
    

    这行代码创建了一个 shared_ptr,并将其初始化为指向一个名为 “Magic Gem” 的 Treasure 对象。此时,shared_ptr 的引用计数为 1。

  2. 共享 shared_ptr

    {
        std::shared_ptr<Treasure> anotherGem = sharedGem;
        std::cout << "Gem is shared by " << sharedGem.use_count() << " wizards" << std::endl;
    }
    

    在这个代码块中,sharedGem 被赋值给 anotherGem,使得 anotherGem 也指向同一个 Treasure 对象。这会使 sharedGem 的引用计数增加到 2。

    当这个代码块结束时,anotherGem 超出作用域并被销毁,其持有的引用也会被释放,引用计数将减少为 1。

  3. 检查引用计数:

    std::cout << "Now Gem is shared by " << sharedGem.use_count() << " wizard" << std::endl;
    

    这行代码在 anotherGem 销毁后打印出当前 sharedGem 的引用计数,应该显示为 1。

  4. 返回语句:

    return 0;
    

    在程序结束时,main 函数的最后一个语句是 return 0。由于 sharedGem 是在 main 函数作用域内定义的局部变量,它会在 main 函数的作用域结束时自动销毁。当 sharedGem 销毁时,其引用计数会降到 0,因此它管理的 “Magic Gem” 对象也会被销毁。

作用域的理解

  • sharedGem 的生命周期贯穿整个 main 函数。当 main 函数执行完毕(即执行 return 0 时),sharedGem 超出作用域并被销毁。
  • 在 C++ 中,局部变量的生命周期受其所在的代码块(或函数)的作用域控制。当程序执行到作用域的结束(通常是右花括号 } 处)时,该作用域内定义的局部变量会自动被销毁。

为什么 sharedGemreturn 0 之后才被销毁?

  • sharedGem 是在 main 函数中定义的局部变量,它的生命周期与 main 函数的执行周期一致。
  • main 函数即将结束时,所有在 main 函数作用域内定义的局部变量都会被销毁。因此,sharedGem 会在 return 0 之后销毁,因为此时 main 函数的作用域结束。
  • sharedGem 销毁时,其引用计数降为 0,导致 “Magic Gem” 对象被销毁。

总结

  • shared_ptr 对象在其作用域结束时会自动销毁,这通常发生在函数结束或显式块(用 {} 包裹的代码块)结束时。
  • shared_ptr 的引用计数降为 0 时,它所管理的对象会自动释放。在这个例子中,sharedGemreturn 0 后才被销毁,因为它是 main 函数的局部变量,而 main 函数结束意味着作用域的结束。

5. std::weak_ptr

weak_ptr 像是一个没有实际魔力的观察者。它可以查看 shared_ptr 管理的物品,但不会增加引用计数。

#include <memory>
#include <iostream>

class Treasure {
public:
    Treasure(std::string n) : name(n) {
        std::cout << "Treasure " << name << " created!" << std::endl;
    }
    ~Treasure() {
        std::cout << "Treasure " << name << " destroyed!" << std::endl;
    }
    std::string name;
};

int main() {
    std::weak_ptr<Treasure> weakGem;
    
    {
        std::shared_ptr<Treasure> sharedGem = std::make_shared<Treasure>("Magic Gem");
        weakGem = sharedGem;
        
        if (auto temp = weakGem.lock()) {
            std::cout << "I can see the " << temp->name << std::endl;
        }
    }
    
    if (weakGem.expired()) {
        std::cout << "The gem has vanished!" << std::endl;
    }
    
    return 0;
}

输出:

Treasure Magic Gem created!
I can see the Magic Gem
Treasure Magic Gem destroyed!
The gem has vanished!

weak_ptr 允许我们观察一个对象,而不会影响它的生命周期。

6. 智能指针的使用建议

  1. 优先使用 unique_ptr,除非你确实需要共享所有权。
  2. 使用 make_uniquemake_shared 来创建智能指针,而不是直接使用 new
  3. 避免使用裸指针,尽可能使用智能指针。
  4. 使用 weak_ptr 来解决循环引用问题。

7. 注意事项

  1. 不要手动 delete 智能指针管理的对象。
  2. 避免将同一个裸指针赋值给多个智能指针。
  3. 小心使用 get() 方法,不要用它返回的裸指针来创建另一个智能指针。

总结

智能指针就像是C++世界中的自动清洁魔法师。它们帮助我们管理内存,防止内存泄漏,使得代码更安全、更易于维护。通过正确使用 unique_ptrshared_ptrweak_ptr,我们可以创建出更加健壮和高效的C++程序。记住,在这个魔法世界中,让智能指针来处理你的"垃圾",你就可以专注于创造更多精彩的"魔法"了!

另一种讲解

shared_ptr 是 C++ 标准库中的一种智能指针,主要用于管理动态分配的内存。它可以自动管理对象的生命周期,避免内存泄漏。当多个 shared_ptr 指向同一个对象时,该对象的引用计数会增加。当最后一个 shared_ptr 被销毁或重置时,引用计数降为零,所指向的对象会被自动释放。

为了更好地理解 shared_ptr 的工作原理及其引用计数机制,下面通过一个简单的示例来详细说明。

示例代码

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() {
        std::cout << "MyClass Constructor\n";
    }
    ~MyClass() {
        std::cout << "MyClass Destructor\n";
    }
    void display() {
        std::cout << "Hello from MyClass\n";
    }
};

int main() {
    std::shared_ptr<MyClass> ptr1(new MyClass());  // 创建一个shared_ptr,引用计数为1
    {
        std::shared_ptr<MyClass> ptr2 = ptr1;  // ptr2也指向同一个对象,引用计数增加到2
        std::cout << "Reference Count: " << ptr1.use_count() << std::endl;  // 输出引用计数

        ptr2->display();  // 使用ptr2访问对象的成员函数
    }  // 离开作用域,ptr2被销毁,引用计数减少到1

    std::cout << "Reference Count: " << ptr1.use_count() << std::endl;  // 输出引用计数

    ptr1.reset();  // 手动重置ptr1,引用计数减少到0,MyClass对象被销毁

    return 0;
}

逐步解析

  1. 创建 shared_ptr

    std::shared_ptr<MyClass> ptr1(new MyClass());
    
    • 这里我们创建了一个 shared_ptr 对象 ptr1,指向一个动态分配的 MyClass 实例。此时,引用计数为 1。
    • 输出结果:MyClass Constructor
  2. 复制 shared_ptr

    std::shared_ptr<MyClass> ptr2 = ptr1;
    
    • 现在我们创建了另一个 shared_ptr 对象 ptr2,它指向同一个 MyClass 实例。引用计数增加到 2。
    • 通过 ptr1.use_count()ptr2.use_count() 可以得到当前的引用计数,即 2。
    • 输出结果:Reference Count: 2
  3. 使用 shared_ptr 访问对象成员:

    ptr2->display();
    
    • 我们可以使用 ptr2 访问 MyClass 对象的成员函数 display()
    • 输出结果:Hello from MyClass
  4. 超出作用域销毁 shared_ptr

    • 当代码块执行完毕后,ptr2 离开作用域被销毁,引用计数自动减少到 1。
    • 再次输出引用计数,即 1。
    • 输出结果:Reference Count: 1
  5. 手动重置 shared_ptr

    ptr1.reset();
    
    • 调用 reset() 方法将 ptr1 重置为空指针。此时,引用计数减少到 0。
    • 当引用计数为 0 时,shared_ptr 会自动销毁所管理的对象,即 MyClass 实例的析构函数被调用。
    • 输出结果:MyClass Destructor

完整输出结果

MyClass Constructor
Reference Count: 2
Hello from MyClass
Reference Count: 1
MyClass Destructor

关键点总结

  • 引用计数的变化: 每当一个新的 shared_ptr 指向同一个对象时,引用计数增加;每当一个 shared_ptr 被销毁或重置时,引用计数减少。
  • 对象的生命周期管理: 当引用计数降为 0 时,shared_ptr 会自动释放所管理的对象,避免内存泄漏。
  • 作用域的影响: shared_ptr 的生命周期与作用域紧密相关,当超出作用域时,指针会被自动销毁,并更新引用计数。

这个简单的示例说明了 shared_ptr 的基本用法以及它如何帮助管理动态内存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值