C/C++培训
达内IT学院
400-996-5531
长久以来 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
填写下面表单即可预约申请免费试听!怕钱不够?可就业挣钱后再付学费! 怕学不会?助教全程陪读,随时解惑!担心就业?一地学习,可全国推荐就业!
Copyright © 京ICP备08000853号-56 京公网安备 11010802029508号 达内时代科技集团有限公司 版权所有
Tedu.cn All Rights Reserved