C++ 中的智能指针

本文深入探讨C++11中引入的智能指针概念,包括std::unique_ptr、std::shared_ptr和std::weak_ptr的使用方法及特点。重点介绍了它们如何帮助解决内存管理和资源释放的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C++ 中的智能指针

长久以来 C++ 最被人诟病的就是它的内存管理,写个稍微复杂点的程序就经常会碰到内存泄漏问题。为了解决这个问题,C++ 也做了许多努力。

在 C++98 标准中首先提出了智能指针的概念,引入了 auto_ptr。但是在实践中,auto_ptr 有不少问题。因此在 C++11 标准中对原有的智能指针又做了进一步的升级,根据应用场景的不同,分成了 shared_ptr, weak_ptr, unique_ptr 三个智能指针类。

本文就来介绍一下这些指针类。

std::unique_ptr

C++11 中引入的几个智能指针的声明在 memory 中。因此要使用智能指针需要包含这个头文件。

#include <memory>

unique_ptr 顾名思义它具有某种唯一性。这种智能指针不共享它内部的指针。无法复制到其他 unique_ptr,无法通过值传递到函数。但是可以通过 C++11 中新引入的移动语义移动给另一个 unique_ptr,当然移动之后它本身内部指针所对应的内存资源就转移出去了,它自己就变成了一个空指针了。

下面是一个最简单的例子,演示了两种初始化 unique_ptr 智能指针的方法。

struct B {
  virtual void bar() { std::cout << "B::bar\n"; }
  virtual ~B(){ std::cout << "B::~B\n";  }
};
int main()
{
    {
        std::unique_ptr<B> p1(new B);
        std::unique_ptr<B> p2 = std::make_unique<B>(); 
        p2->bar();
    }
    getchar();
}

运行的结果如下:

B::bar
B::~B
B::~B

可以看到,两个 B 的实例都自动的调用了析构函数,因此我们就不需要自己去 delete 了。

下面再用一个简单的例子演示一下移动语义。

int main()
{
    {
        std::unique_ptr<B> p1(new B); // p is a unique_ptr that owns a B
        std::unique_ptr<B> p2 = std::make_unique<B>(); // p is a unique_ptr that owns a B
        p2->bar();

        std::cout << "before move" << std::endl;
        p2 = std::move(p1);
        //p2 = p1;// 这句是错的,unique_ptr 不支持拷贝。
        std::cout << "after move" << std::endl;

        //p1->bar(); 执行这句程序会出错退出
    }
    getchar();
}

运行结果如下:

B::bar
before move
B::~B
after move
B::~B

可以看到 p1 move 给 p2 后,p2 原先指向的对象就被析构掉了,同时 p1 拥有的对象也转移了。这之后 p1 就是空指针了。

使用 unique_ptr 并不会影响 C++ 对多态的支持。比如下面这个例子:

struct B {
  virtual void bar() { std::cout << "B::bar\n"; }
  virtual ~B(){ std::cout << "B::~B\n";  }
};
struct D : B
{
    D() { std::cout << "D::D\n";  }
    ~D() { std::cout << "D::~D\n";  }
    void bar() override { std::cout << "D::bar\n";  }
};

int main()
{
    {
        std::unique_ptr<B> p1(new D); // p is a unique_ptr that owns a B
        std::unique_ptr<B> p2 = std::make_unique<B>(); // p is a unique_ptr that owns a B
        p1->bar();

        std::cout << "before move" << std::endl;
        p2 = std::move(p1);
        //p2 = p1;// 这句是错的,unique_ptr 不支持拷贝。
        std::cout << "after move" << std::endl;

        //p1->bar(); 执行这句程序会出错退出
    }
    getchar();
}

执行结果如下:

D::D
D::bar
before move
B::~B
after move
D::~D
B::~B

下面来演示一下通过函数传递 unique_ptr 的方法:

std::unique_ptr<D> pass_through(std::unique_ptr<D> p)
{
    p->bar();
    return p;
}

int main()
{
    {
        std::unique_ptr<D> p1 = std::make_unique<D>(); // p is a unique_ptr that owns a D
        std::unique_ptr<D> p2 = pass_through(std::move(p1));
        assert(!p1); // now p owns nothing and holds a null pointer
        p2->bar();   // and q owns the D object
    }
    getchar();
}

运行的结果如下:

D::D
D::bar
D::bar
D::~D
B::~B

我们知道派生类的指针可以赋值给基类指针。unique_ptr 也支持这么做。比如下面的代码片段:

std::unique_ptr<D> p1 = std::make_unique<D>(); // p is a unique_ptr that owns a D
std::unique_ptr<B> p2 = std::move(p1);

但是如果反着写就无法编译了:

std::unique_ptr<B> p1 = std::make_unique<B>(); 
std::unique_ptr<D> p2 = std::move(p1);

unique_ptr 可以放到容器中,下面是例子:

int main()
{
    {
        std::vector<std::unique_ptr<B>> v;  // unique_ptr can be stored in a container
        v.push_back(std::make_unique<D>());
        std::unique_ptr<B> p = std::make_unique<B>();
        v.push_back(std::move(p));
        v.emplace_back(new D);
        for(auto& p: v) p->bar();
    }
    getchar();
}

运行结果如下:

D::D
D::D
D::bar
B::bar
D::bar
D::~D
B::~B
B::~B
D::~D
B::~B

unique_ptr 还支持new 多个对象,比如下面的代码:

int main()
{
    {
        std::unique_ptr<D[]> p{new D[3]};
        p[1].bar();
    }
    getchar();
}

运行结果如下:

D::D
D::D
D::D
D::bar
D::~D
B::~B
D::~D
B::~B
D::~D
B::~B

上面的例子中,unique_ptr 指向的内存都是在 unique_ptr 离开作用域时释放的。但是有时我们需要提前释放,这时就可以用 reset() 方法。比如 p 是个 unique_ptr,那么 p.reset() 就释放了 p 中指向的内存资源。

另一个常用的函数是 swap,这个可以交换两个 unique_ptr,比如下面的例子:

struct B 
{
    B(char x) {c = x;}
    virtual void bar() { std::cout << "B::bar, c =" << c << "\n"; }
    virtual ~B(){ std::cout << "B::~B\n";  }
    char c;
};


int main()
{
    {
        std::unique_ptr<B> p1(new B('1'));
        std::unique_ptr<B> p2(new B('2'));

        p1->bar();
        swap(p1, p2);
        p1->bar();
    }
    getchar();
}

运行结果如下:

B::bar, c =1
B::bar, c =2
B::~B
B::~B

至此,unique_ptr 的基本用法就介绍完了,下面给出 unique_ptr 两种形式,分别对应 new 和 new[]。

template<
    class T,
    class Deleter = std::default_delete<T>
> class unique_ptr;

template <
    class T,
    class Deleter
> class unique_ptr<T[], Deleter>;

可以看到,我们还留下个 Deleter 没有介绍,这个 Deleter 通常是用 lambda 表达式来实现的。有兴趣的同学可以下面的网址来学习:
http://en.cppreference.com/w/cpp/memory/unique_ptr

可以说,unique_ptr 是用来取代 C++98 的 auto_ptr。在程序代码中,遇到指针的地方,首选 unique_ptr,当 unique_ptr 不满足需求时才考虑下面这两种智能指针。

std::shared_ptr

这种指针允许多个 shared_ptr 拥有同一个内存资源。shared_ptr 内部维持着对拥有的内存资源的引用计数。比如有 5个 shared_ptr 拥有同一个内存资源,那么这个引用计数就是 5。这时如果一个 shared_ptr 离开了它的作用域,那么就还剩下 4 个 shared_ptr 拥有这个内存资源,引用计数就变为了 4。 当引用计数下降到 0 时,这个内存资源就被释放了。

下面是个简单的例子,给出了使用 shared_ptr 的基本方法:

using namespace std;
int main()
{
    {
        shared_ptr<string> s1(new string("s1"));
        shared_ptr<string> s2 = make_shared<string>("s2");

        cout << *s1 << endl;
        cout << *s2 << endl;

        s1 = s2;

        cout << s1.use_count() << endl;
    }
    getchar();
}

结果如下:

s1
s2
2

std::weak_ptr

weak_ptr 并不具备指针的全部功能,比如因为它没有重载 operator* 和 ->,所以不能直接访问内存资源,但是 weak_ptr 可以转化为 shared_ptr,之后就能访问资源了。我们可以认为 weak_ptr 是一种用来辅助 shared_ptr 的辅助类。

weak_ptr 对对象是弱引用,不会增加对象的引用计数,shared_ptr 可以直接赋值给它,它可以通过调用 lock 函数来获得 shared_ptr。

这个指针相对来说不是那么常用,所以就不多介绍了。
下面给个例子,介绍 weak_ptr 和 shared_ptr 的转换:

#include <iostream>
#include <memory>

std::weak_ptr<int> gw;

void f()
{
    std::cout << "use_count == " << gw.use_count() << ": ";
    if (auto spt = gw.lock()) { // Has to be copied into a shared_ptr before usage
        std::cout << *spt << "\n";
    }
    else {
        std::cout << "gw is expired\n";
    }
}

int main()
{
    {
        auto sp = std::make_shared<int>(42);
        gw = sp;

        f();
    }

    f();
}

运行结果如下:

use_count == 1: 42
use_count == 0: gw is expired
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值