更多课程 选择中心

C/C++培训
达内IT学院

400-111-8989

C++培训教程:C++ 中的智能指针知识点

  • 发布:C++培训
  • 来源:学习笔记
  • 时间:2017-09-22 16:15

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

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

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

std::unique_ptr

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

#include <memory>1

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::~B12345

可以看到 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);12

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

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

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

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::~B1234

至此,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>;123456789

可以看到,我们还留下个 Deleter 没有介绍,这个 Deleter 通常是用 lambda 表达式来实现的。有兴趣的同学可以下面的网址来学习:

#/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

2123

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 expired12

预约申请免费试听课

填写下面表单即可预约申请免费试听!怕钱不够?可就业挣钱后再付学费! 怕学不会?助教全程陪读,随时解惑!担心就业?一地学习,可全国推荐就业!

上一篇:C/C++开发程序员这些基础必须熟记!
下一篇:C/C++数组知识,C/C++数组问题知识总结

C语言创建windows窗口实例

C++回调函数是什么?

C++ shared_ptr和动态数组

C语言有哪些关键词,C语言44个关键词大全

  • 扫码领取资料

    回复关键字:视频资料

    免费领取 达内课程视频学习资料

  • 搜索抖音号

    搜索抖音号:1821685962

    免费领取达内课程视频学习资料

Copyright © 2021 Tedu.cn All Rights Reserved 京ICP备08000853号-56 京公网安备 11010802029508号 达内时代科技集团有限公司 版权所有

选择城市和中心
黑龙江省

吉林省

河北省

湖南省

贵州省

云南省

广西省

海南省