您的位置:首页 > 编程语言 > C语言/C++

动态内存与智能指针

2015-03-20 21:25 183 查看
在C++中,动态内存的管理通过一对运算符来完成的:new, 在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化;delete,接受一个对象的指针,销毁该对象,并释放与之关联的内存。

动态内存的使用很容易出现问题,我们假设这样一种应用环境。假设程序通过工厂函数(factory function)供应我们特定的Investment对象。

class Investment{ ... };         // 投资类型,root class

Investment* createInvestment();  // 返回指针,指向Investment对象

void f( )
{
Investment *pInv = createInvestment( );   // 调用factory函数
...
delete pInv;                              // 释放pInv所指对象
}


这看起来妥当,但若干情况下f可能无法删除它得自createInvestment 的投资对象——或许因为”…”区域内的一个过早的return语句。如果这样一个return语句被执行起来,控制流不会触及delete语句。类似情况发生在对createInvestment的使用及delete动作位于某循环内,而该循环由于某个continue或goto语句过早退出。最后一种可能是”…”区域内的语句抛出异常,果真如此控制流再次不会临幸delete,无论delete如何被略过去,我们泄漏的不只是内含投资对象的那块内存,还包括哪些投资对象所保存的任何资源。

谨慎地编写程序可以防止这一类错误,但为了更容易地使用动态内存,并防止此类错误的发生,C++新的标准库提出了两种智能指针(smart pointer)类型来管理动态对象。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。新的标准库提供的两种智能指针的区别在于管理底层指针的方式:shared_ptr允许多个指针指向同一个对象;unique_ptr 则”独占“所指向的对象。标准库还定义了一个名为 weak_ptr 的伴随类,它是一种弱引用,指向 shared_ptr所管理的对象。这三种类型都在memory头文件中。

1 shared_ptr

智能指针是一个模板。

shared_ptr<string> p1;        // 可以指向string
shared_ptr<list<int>> p2;     // 可以指向int的list


默认初始化的智能指针中保存着一个空指针。

表1.1 shared_ptrunique_ptr 都支持的操作

操作解释
share_ptr<T> sp
空智能指针
unique_ptr<T> up
空智能指针
p
将p用作条件判断,若p指向一个对象,则为true
*p
解引用p,获得它指向的对象
p->mem
等价于
(*p).mem
p.get()
返回p中保存的指针。
swap(p, q); p.swap(q)
交换p和q中的指针
表1.2 shared_ptr 独有的操作

操作解释
make_shared<T>(args)
返回一个
shared_ptr
,指向一个动态分配的类型为T的对象。使用args初始化对象
shared_ptr<T> p(q)
p是
shared_ptr
的拷贝
p = q
p和q都是shared_ptr,所保存的指针必须能相互转换。此操作会递减p的引用次数,递增q的引用次数。若p的引用次数为0,则将其管理的内存释放
p.unique()
p.use_count()
为1,返回true,否则返回false
p.use_count()
返回p共享对象的智能指针数量,可能很慢,主要用于调试

1.1 make_shared函数

最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。此函数在动态内存中分配一个对象并初始化它,返回指向对象的
shared_ptr
。当要用make_shared时, 必须指定想要创建的对象类型,其用法如下:

// 指向一个值为42的int的shared_ptr
shared_ptr<int> p3 = make_shared<int>(42);
// p4指向一个值为"999"的string
shared_ptr<string> p4 = make_shared<string>("999");
// p5指向一个值初始化的int,即,值为0
shared_ptr<int> p5 = make_shared<int>();


1.2 shared_ptr的拷贝和赋值

当进行拷贝或赋值时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同对象;

auto p = make_shared<int> (42);  // p指向的对象只有一个引用者
auto q(p);  // p和q指向相同对象,此对象有两个引用


我们可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数。无论何时我们拷贝一个shared_ptr,计数器都会递增。当我们给一个shared_ptr赋予一个新值或是shared_ptr被销毁时,计数器就会递减。

一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象:

auto r = make_shared<int> (42);  // r指向的int只有一个引用者
r = q; // 给r赋值,令它指向另一个地址
// 递增q指向的对象的引用计数
// 递减r原来指向的对象的引用计数
// r原来指向的对象已没有引用者,会自动释放


此例中我们分配了一个int,将其指针保存在r中。接下来,我们将一个新值赋予r。在此情况下,r是唯一指向此int的shared_ptr,在把q赋给r的过程中,此int被自动释放。

1.3 shared_ptr自动销毁所管理的对象

当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象。它是通过另一个特殊的成员函数——析构函数完成销毁工作的。

shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它所占用的内存。

当动态对象不再被使用时,shared_ptr类会自动释放动态对象,这一特性使得动态内存的使用变得非常容易。

2 unique_ptr类

一个unique_ptr”拥有”它所指向的对象。与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁。表2.1 列出了unique_ptr特有的操作。与shared_ptr相同的操作在表1.1中。

表2.1 unique_ptr 独有的操作

操作解释
unique_ptr<T> u1
空unique_ptr,可以指向类型为T的对象。u1会使用delete来释放它的指针
unique_ptr<T, D> u2
空unique_ptr,可以指向类型为T的对象。u2会使用一个类型为D的可调用对象来释放它的指针
unique_ptr<T, D> u(d)
空unique_ptr,指向类型为T的对象,用类型为D的对象d代替delete
u = nullptr
释放u指向的对象,将u置为空
u.release()
u放弃对指针的控制权,返回指针,并将u置为空
u.reset()
释放u指向的对象
u.reset(q)
如果提供了内置指针q,令u指向这个对象;否则将u置为空
u.reset(nullptr)
与shared_ptr不同,没有类似的make_shared的标准库函数返回一个unique_ptr。当我们定义一个unique_ptr时,需要将其绑定到一个new返回的指针上。类似shared_ptr,初始化unique_ptr必须采用直接初始化形式:

unique_ptr<double> p1;               // 可以指向一个double的unique_ptr
unique_ptr<int> p2(new int(42));     // p2指向一个值为42的int


由于一个unique_ptr拥有它指向的对象,因此它不支持普通的拷贝或赋值操作:

unique_ptr<string> p1(new string("hello"));
unique_ptr<string> p2(p1);    // 错误:unique_ptr不支持拷贝
unique_ptr<string> p3;
p3 = p2;                      // 错误:unique_ptr不支持赋值


虽然不支持拷贝或赋值unique_ptr,但可以通过调用release或reset将指针的所有权从一个(非cons)unique_ptr转移给另一个unique_ptr:

unique_ptr<string> p2(p1.release());     // release将p1置为空
unique_ptr<string> p3(new string("world"));
// 将所有权从p3转移到p2
p2.reset(p3.release() );    // reset释放了p2原来指向的内存


2.1传递unique_ptr参数和返回unique_ptr

不能拷贝unique_ptr的规则有一个例外:我们可以拷贝或赋值一个将要销毁的unique_ptr。最常见的例子是从函数返回一个unique_ptr:

unique_ptr<int> clone(int p)  {
//  正确:从一个int*创建一个unique_ptr<int>
return unique_ptr<int> ret(new int(p));
}


还可以返回一个局部对象的拷贝:

unique_ptr<int> clone(int p)  {
unique_ptr<int> ret(new int(p));
// ...
return ret;
}


在这两个例子中,编译器都知道要返回的对象将要被销毁,在此情况下,编译器执行一种特殊的拷贝。

unique_ptr向后兼容auto_ptr

标准库的较早版本包含了一个名为auto_ptr的类,它具有unique_ptr的部分特性,但不是全部。特别是,我们不能在容器中保存auto_ptr,也不能从函数返回auto_ptr。虽然auto_ptr仍是标准的一部分,但编写程序是应该用unique_ptr。

3 weak_ptr类

weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放,因此,weak_ptr的名字抓住了这种智能指针”弱“共享对象的特点。

auto p = make_shared<int>(42);
weak_ptr<int> wp(p);      // wp弱共享p;p的引用计数为改变


本文转自C++primer(第五版)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息