问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

智能指针:从基础、使用选择到原理

创作时间:
作者:
@小白创作中心

智能指针:从基础、使用选择到原理

引用
1
来源
1.
https://cloud.tencent.com/developer/article/2483477

C++11引入的智能指针是现代C++编程中的重要特性,它不仅简化了内存管理,还提高了代码的安全性和可读性。本文将详细介绍三种智能指针(std::unique_ptr、std::shared_ptr和std::weak_ptr)的特性和使用场景,并深入探讨它们的实现原理。

智能指针概述

C++11引入了智能指针的概念,它是一种对象,当其作用域结束时,它会自动删除所指向的对象。智能指针有助于防止内存泄漏,它们封装了原始指针,使得内存管理更加自动化。C++11提供了三种类型的智能指针:

  • std::unique_ptr
  • std::shared_ptr
  • std::weak_ptr

std::unique_ptr的特性和使用

std::unique_ptr是一种独占所有权的智能指针,它禁止共享其指向的对象。这意味着在任何时候,只能有一个std::unique_ptr指向给定的对象。

#include <memory>
std::unique_ptr<int> ptr(new int(5));  // ptr现在拥有一个int对象
std::unique_ptr<int> ptr2 = ptr;  // 错误!不能复制unique_ptr

std::shared_ptr的特性和使用

std::shared_ptr允许多个指针指向同一个对象。这是通过引用计数实现的,即每个shared_ptr都有一个计数器,当我们复制一个shared_ptr时,计数器就会增加,当我们销毁一个shared_ptr时,计数器就会减少。当计数器变为0时,对象就会被删除。

#include <memory>
std::shared_ptr<int> ptr(new int(5));  // ptr现在拥有一个int对象
std::shared_ptr<int> ptr2 = ptr;  // ptr2和ptr现在都指向同一个int对象,计数器为2

std::weak_ptr的特性和使用

std::weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个由std::shared_ptr管理的对象。将std::weak_ptr看作是std::shared_ptr的安全观察者,它不会增加引用计数。

#include <memory>
std::shared_ptr<int> ptr(new int(5));  // ptr现在拥有一个int对象
std::weak_ptr<int> wptr = ptr;  // wptr是对ptr的观察者,不会增加计数器

智能指针的使用案例

以下是一个使用std::shared_ptr的例子,展示了如何使用智能指针来管理内存。

#include <iostream>
#include <memory>
class MyClass {
public:
    MyClass() { std::cout << "MyClass constructed\n"; }
    ~MyClass() { std::cout << "MyClass destroyed\n"; }
};
int main() {
    std::shared_ptr<MyClass> ptr(new MyClass());  // MyClass对象现在被ptr管理
    {
        std::shared_ptr<MyClass> ptr2 = ptr;  // ptr2和ptr现在都管理MyClass对象,计数器为2
    }  // ptr2离开作用域,MyClass对象的计数器变为1
}  // ptr离开作用域,MyClass对象的计数器变为0,对象被删除

在这个例子中,当ptr2离开其作用域时,MyClass对象不会被删除,因为ptr仍然在管理它。只有当ptr也离开其作用域时,MyClass对象才会被删除。

解决循环引用问题:std::weak_ptr的引入

std::weak_ptr的引入主要是为了解决std::shared_ptr可能产生的循环引用问题。循环引用是指两个或多个std::shared_ptr对象互相引用,形成一个闭环,导致引用计数永远不会降为0,从而引发内存泄漏。

例如,假设我们有两个类A和B,它们互相包含对方的std::shared_ptr:

class B;
class A {
public:
    std::shared_ptr<B> b_ptr;
};
class B {
public:
    std::shared_ptr<A> a_ptr;
};

如果我们创建A和B的实例,并使它们互相引用:

std::shared_ptr<A> a(new A());
std::shared_ptr<B> b(new B());
a->b_ptr = b;
b->a_ptr = a;

这将导致一个循环引用,即使我们销毁了a和b,A和B的实例也不会被删除,因为它们的引用计数永远不会降为0。

这就是std::weak_ptr的用武之地。std::weak_ptr不会增加引用计数,因此不会引发循环引用问题。我们可以将上述代码修改为:

class B;
class A {
public:
    std::weak_ptr<B> b_ptr;  // 使用weak_ptr代替shared_ptr
};
class B {
public:
    std::shared_ptr<A> a_ptr;
};

这样,即使A和B互相引用,也不会形成循环引用,从而避免了内存泄漏。

选择std::unique_ptr还是std::shared_ptr

选择std::unique_ptr还是std::shared_ptr,主要取决于你的所有权策略。

  • 如果你的数据需要被多个指针共享,std::shared_ptr是一个不错的选择。std::shared_ptr使用计数机制来确保只有最后一个指针被销毁时,其指向的对象才会被删除。
  • 如果你的数据只需要一个指针拥有,那么std::unique_ptr是一个更好的选择。它不需要额外的计数开销,因此比std::shared_ptr更轻量。

在大多数情况下,你应该默认使用std::unique_ptr,除非你知道你需要一个std::shared_ptr。这是因为std::unique_ptr更轻量,而且不需要额外的计数开销。此外,使用std::unique_ptr可以更清楚地表达所有权语义。

案例

假设我们有一个class Widget,并且我们有一个WidgetFactory类,它有一个createWidget函数,该函数返回一个新创建的Widget对象。在这种情况下,我们应该返回一个std::unique_ptr,因为工厂创建的每个小部件都是唯一的,不应该被共享。

class Widget { /* ... */ };
class WidgetFactory {
public:
    std::unique_ptr<Widget> createWidget() {
        return std::unique_ptr<Widget>(new Widget());
    }
};

另一方面,如果我们有一个class SharedData,这个类的对象需要被多个其他对象共享,那么我们应该使用std::shared_ptr

class SharedData { /* ... */ };
class DataUser {
public:
    void setData(const std::shared_ptr<SharedData>& data) {
        m_data = data;
    }
private:
    std::shared_ptr<SharedData> m_data;
};

在这个例子中,多个DataUser对象可以共享同一个SharedData对象,当最后一个使用它的DataUser被销毁时,SharedData对象也会被删除。

智能指针的底层实现原理

智能指针的底层实现原理主要依赖于两个C++特性:模板和析构函数。

  • 模板:智能指针是模板类,可以接受任何类型的原始指针。
  • 析构函数:当智能指针的实例离开其作用域时,其析构函数会被自动调用,从而删除其所指向的对象。

例如,std::shared_ptr的简化实现可能如下:

template <typename T>
class shared_ptr {
public:
    shared_ptr(T* ptr) : ptr_(ptr), count_(new int(1)) {}
    ~shared_ptr() {
        if (--*count_ == 0) {
            delete ptr_;
            delete count_;
        }
    }
    shared_ptr(const shared_ptr& other) : ptr_(other.ptr_), count_(other.count_) {
        ++*count_;
    }
private:
    T* ptr_;
    int* count_;
};

这个简化的shared_ptr实现在构造函数中接受一个原始指针,并初始化引用计数为1。在析构函数中,它会减少引用计数,如果引用计数为0,就删除所指向的对象。在复制构造函数中,它会增加引用计数。

智能指针的设计模式

智能指针的实现使用了设计模式中的“策略模式”。策略模式定义了一系列的算法,并将每一个算法封装起来,使它们可以互相替换。在这里,每种智能指针(std::unique_ptr,std::shared_ptr,std::weak_ptr)都实现了自己的内存管理策略,但它们都有相同的接口,因此可以互相替换使用。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号