动态内存与智能指针
2015-03-20 21:25
183 查看
在C++中,动态内存的管理通过一对运算符来完成的:new, 在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化;delete,接受一个对象的指针,销毁该对象,并释放与之关联的内存。
动态内存的使用很容易出现问题,我们假设这样一种应用环境。假设程序通过工厂函数(factory function)供应我们特定的Investment对象。
这看起来妥当,但若干情况下f可能无法删除它得自createInvestment 的投资对象——或许因为”…”区域内的一个过早的return语句。如果这样一个return语句被执行起来,控制流不会触及delete语句。类似情况发生在对createInvestment的使用及delete动作位于某循环内,而该循环由于某个continue或goto语句过早退出。最后一种可能是”…”区域内的语句抛出异常,果真如此控制流再次不会临幸delete,无论delete如何被略过去,我们泄漏的不只是内含投资对象的那块内存,还包括哪些投资对象所保存的任何资源。
谨慎地编写程序可以防止这一类错误,但为了更容易地使用动态内存,并防止此类错误的发生,C++新的标准库提出了两种智能指针(smart pointer)类型来管理动态对象。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。新的标准库提供的两种智能指针的区别在于管理底层指针的方式:shared_ptr允许多个指针指向同一个对象;unique_ptr 则”独占“所指向的对象。标准库还定义了一个名为 weak_ptr 的伴随类,它是一种弱引用,指向 shared_ptr所管理的对象。这三种类型都在memory头文件中。
默认初始化的智能指针中保存着一个空指针。
表1.1 shared_ptr 和 unique_ptr 都支持的操作
表1.2 shared_ptr 独有的操作
我们可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数。无论何时我们拷贝一个shared_ptr,计数器都会递增。当我们给一个shared_ptr赋予一个新值或是shared_ptr被销毁时,计数器就会递减。
一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象:
此例中我们分配了一个int,将其指针保存在r中。接下来,我们将一个新值赋予r。在此情况下,r是唯一指向此int的shared_ptr,在把q赋给r的过程中,此int被自动释放。
shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它所占用的内存。
当动态对象不再被使用时,shared_ptr类会自动释放动态对象,这一特性使得动态内存的使用变得非常容易。
表2.1 unique_ptr 独有的操作
与shared_ptr不同,没有类似的make_shared的标准库函数返回一个unique_ptr。当我们定义一个unique_ptr时,需要将其绑定到一个new返回的指针上。类似shared_ptr,初始化unique_ptr必须采用直接初始化形式:
由于一个unique_ptr拥有它指向的对象,因此它不支持普通的拷贝或赋值操作:
虽然不支持拷贝或赋值unique_ptr,但可以通过调用release或reset将指针的所有权从一个(非cons)unique_ptr转移给另一个unique_ptr:
还可以返回一个局部对象的拷贝:
在这两个例子中,编译器都知道要返回的对象将要被销毁,在此情况下,编译器执行一种特殊的拷贝。
unique_ptr向后兼容auto_ptr
标准库的较早版本包含了一个名为auto_ptr的类,它具有unique_ptr的部分特性,但不是全部。特别是,我们不能在容器中保存auto_ptr,也不能从函数返回auto_ptr。虽然auto_ptr仍是标准的一部分,但编写程序是应该用unique_ptr。
本文转自C++primer(第五版)。
动态内存的使用很容易出现问题,我们假设这样一种应用环境。假设程序通过工厂函数(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_ptr 和 unique_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中的指针 |
操作 | 解释 |
---|---|
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) |
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(第五版)。
相关文章推荐
- 智能指针的模板,用来管理动态分配的内存
- C++中的动态内存与智能指针
- C++动态内存和智能指针
- c++之智能指针(一)之动态内存与智能指针
- 动态内存和智能指针
- c++ primer(第五版)学习笔记及习题答案代码版(第十二章)动态内存与智能指针
- 动态内存与智能指针
- 浅谈C++普通指针和智能指针管理动态内存的陷阱
- 动态内存和智能指针
- C++中的动态内存与智能指针
- 动态内存与智能指针
- 动态内存与智能指针
- 动态内存管理和智能指针 2.0 -- shared_ptr
- C++中的动态内存与智能指针
- 动态内存与智能指针
- 智能指针模板,要管理动态分配的内存
- 动态内存和智能指针
- 动态内存和智能指针
- 动态内存与智能指针
- 动态内存与智能指针及其实现