static_cast 和 dynamic_cast
1.static_cast
static_cast < type-id > ( expression )
该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。
用法:
- 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
- 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
- 把空指针转换成目标类型的空指针。
- 把任何类型的表达式转换成void类型。
注意:static_cast不能转换掉expression的const、volatile、或者__unaligned属性。
2.dynamic_cast
dynamic_cast <type-id> (expression)
将一个基类对象指针(或引用)cast到继承类指针,dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理。
该运算符把expression转换成type-id类型的对象。Type-id 必须是类的指针、类的引用或者void*;
dynamic_cast运算符可以在执行期决定真正的类型。如果 downcast 是安全的(也就说,如果基类指针或者引用确实指向一个派生类对象)这个运算符会传回适当转型过的指针。如果 downcast 不安全,这个运算符会传回空指针(也就是说,基类指针或者引用没有指向一个派生类对象)。
dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
3.static_cast 和 dynamic_cast 的关系
1. 在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
如果 pb 指向一个 D 类型的对象,pd1 和 pd2 是一样的,并且对这两个指针执行 D 类型的任何操作都是安全的;
2.在类层次间进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
如果 pb 指向的是一个 B 类型(基类)的对象,那么 pd1 将是一个指向该对象的指针,对它进行 D 类型的操作将是不安全的(如访问 m_szName),而 pd2 将是一个空指针。
3. static_cast 是在编译时检查,用于非多态的转换,可以转换指针及其他,比如:int->float。
dynamic_cast:运行时检查,用于多态的类型转换(upcast,downcast和crosscast),只能转换指针和引用。
1 2 3 |
|
此时结果,i的值为166。
4. dynamic_cast还支持交叉转换(cross cast)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
在函数foo中,使用static_cast进行转换是不被允许的,将在编译时出错,而使用 dynamic_cast的转换则是允许的,结果是空指针
c++ 智能指针(转)
智能指针的使用
智能指针是在 <memory> 标头文件中的 std
命名空间中定义的。 它们对 RAII 或“获取资源即初始化”编程惯用法至关重要。 此习惯用法的主要目的是确保资源获取与对象初始化同时发生,从而能够创建该对象的所有资源并在某行代码中准备就绪。
实际上,RAII 的主要原则是为将任何堆分配资源(例如,动态分配内存或系统对象句柄)的所有权提供给其析构函数包含用于删除或释放资源的代码以及任何相关清理代码的堆栈分配对象。
大多数情况下,当初始化原始指针或资源句柄以指向实际资源时,会立即将指针传递给智能指针。 在现代 C++ 中,原始指针仅用于范围有限的小代码块、循环或者性能至关重要且不会混淆所有权的 Helper 函数中。
下面的示例将原始指针声明与智能指针声明进行了比较。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
如示例所示,智能指针是你在堆栈上声明的类模板,并可通过使用指向某个堆分配的对象的原始指针进行初始化。 在初始化智能指针后,它将拥有原始的指针。 这意味着智能指针负责删除原始指针指定的内存。
智能指针析构函数包括要删除的调用,并且由于在堆栈上声明了智能指针,当智能指针超出范围时将调用其析构函数,尽管堆栈上的某处将进一步引发异常。
通过使用熟悉的指针运算符(->
和 *
)访问封装指针,智能指针类将重载这些运算符以返回封装的原始指针。
C++ 智能指针思路类似于在语言(如 C#)中创建对象的过程:创建对象后让系统负责在正确的时间将其删除。 不同之处在于,单独的垃圾回收器不在后台运行;按照标准 C++ 范围规则对内存进行管理,以使运行时环境更快速更有效。
重要事项:请始终在单独的代码行上创建智能指针,而绝不在参数列表中创建智能指针,这样就不会由于某些参数列表分配规则而发生轻微泄露资源的情况
下面的示例演示了如何使用标准模板库中的 unique_ptr
智能指针类型将指针封装到大型对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
此示例演示如何使用智能指针执行以下关键步骤。
-
将智能指针声明为一个自动(局部)变量。(不要对智能指针本身使用
new
或malloc
表达式。) -
在类型参数中,指定封装指针的指向类型。
-
在智能指针构造函数中将原始指针传递至
new
对象。(某些实用工具函数或智能指针构造函数可为你执行此操作。) -
使用重载的
->
和*
运算符访问对象。 -
允许智能指针删除对象。
智能指针的设计原则是在内存和性能上尽可能高效。 例如,unique_ptr
中的唯一数据成员是封装的指针。 这意味着,unique_ptr
与该指针的大小完全相同,不是四个字节就是八个字节。
使用重载了 * 和 -> 运算符的智能指针访问封装指针的速度不会明显慢于直接访问原始指针的速度。
智能指针具有通过使用“点”表示法访问的成员函数。 例如,一些 STL 智能指针具有释放指针所有权的重置成员函数。 如果你想要在智能指针超出范围之前释放其内存将很有用,这会很有用,如以下示例所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
智能指针通常提供直接访问其原始指针的方法。 STL 智能指针拥有一个用于此目的的 get
成员函数,CComPtr
拥有一个公共的 p
类成员。
通过提供对基础指针的直接访问,你可以使用智能指针管理你自己的代码中的内存,还能将原始指针传递给不支持智能指针的代码。
1 2 3 4 5 6 7 8 9 10 11 |
|
智能指针的类型
C++ 标准库智能指针
使用这些智能指针作为将指针封装为纯旧 C++ 对象 (POCO) 的首选项。
unique_ptr
只允许基础指针的一个所有者。 除非你确信需要 shared_ptr
,否则请将该指针用作 POCO 的默认选项。 可以移到新所有者,但不会复制或共享。替换已弃用的 auto_ptr
。 与 boost::scoped_ptr
比较。 unique_ptr
小巧高效;大小等同于一个指针且支持 rvalue 引用,从而可实现快速插入和对 STL 集合的检索。 头文件:<memory>
。
unique_ptr 不共享它的指针。 它无法复制到其他 unique_ptr
,无法通过值传递到函数,也无法用于需要副本的任何标准模板库 (STL) 算法。 只能移动unique_ptr
。 这意味着,内存资源所有权将转移到另一 unique_ptr
,并且原始 unique_ptr
不再拥有此资源。 我们建议你将对象限制为由一个所有者所有,因为多个所有权会使程序逻辑变得复杂。 因此,当需要智能指针用于纯 C++ 对象时,可使用 unique_ptr
,而当构造 unique_ptr
时,可使用make_unique Helper 函数。
下图演示了两个 unique_ptr
实例之间的所有权转换。
unique_ptr
在 STL 的 <memory>
标头中定义。 它与原始指针一样有效,并可用于 STL 容器。 将 unique_ptr
实例添加到 STL 容器很有效,因为通过 unique_ptr
的移动构造函数,不再需要进行复制操作。
示例1:
以下示例演示如何创建 unique_ptr
实例并在函数之间传递这些实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
这些示例说明了 unique_ptr
的基本特征:可移动,但不可复制。“移动”将所有权转移到新 unique_ptr
并重置旧 unique_ptr
。
示例2:
以下示例演示如何创建 unique_ptr
实例并在向量中使用这些实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
在 range for 循环中,注意 unique_ptr
通过引用来传递。 如果你尝试通过此处的值传递,由于删除了 unique_ptr
复制构造函数,编译器将引发错误。
示例3:
以下示例演示如何初始化类成员 unique_ptr
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
示例4:
可使用 make_unique 将 unique_ptr
创建到数组,但无法使用 make_unique
初始化数组元素。
1 2 3 4 5 6 7 8 9 |
|
shared_ptr
采用引用计数的智能指针。 如果你想要将一个原始指针分配给多个所有者(例如,从容器返回了指针副本又想保留原始指针时),请使用该指针。 直至所有 shared_ptr
所有者超出了范围或放弃所有权,才会删除原始指针。 大小为两个指针;一个用于对象,另一个用于包含引用计数的共享控制块。 头文件:<memory>
。
shared_ptr
的类型是C + +标准库中一个聪明的指针,是为多个拥有者管理内存中对象的生命周期而设计的。 在你初始化一个 shared_ptr
后,你可以复制它,把函数参数的值递给它,并把它分配给其它 shared_ptr
实例。 所有实例指向同一个对象,并共享访问一个“控制块”,即每当一个新的shared_ptr
被添加时,递增和递减引用计数,超出范围,则复位。 当引用计数到达零时,控制块删除内存资源和自身。
下图显示了指向一个内存位置的几个 shared_ptr
实例。
示例1:
无论什么时候,当内存资源被第一次被创建时,就使用函数 make_shared 创建一个新的 shared_ptr
。 make_shared
异常安全。 它使用同一调用分配的内存控制块和资源从而减少构造开销。 如果你不使用 make_shared
,那么在把它传递给 shared_ptr
的构造函数之前,你必须使用一个明确的新表达式创建的对象。 下面的例子显示了在新对象中声明和初始化一个 shared_ptr
的各种方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
示例2:
下面的示例演示如何声明和初始化一个已经被分配了另一个 shared_ptr
的对象共享所有权的 shared_ptr
的实例。 假设 sp2
是一个初始化的shared_ptr
。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
示例3:
当您使用算法复制元素时,shared_ptr
的也是很有用的标准模板库(STL)。 你可以把元素包装在 shared_ptr
里,然后将其复制到其他容器,只要你需要它,底层的内存始终是有效的。 以下示例演示如何使用 replace_copy_if
算法来创建一个 shared_ptr
的实例以及如何在一个向量上进行使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
示例4:
你可以用 dynamic_pointer_cast
, static_pointer_cast
和 const_pointer_cast
来转换shared_ptr
。 这些函数的操作类似 dynamic_cast
, static_cast
和 const_cast
。 下面的示例演示如何测试在基类的 shared_ptr
向量中的每个元素的派生类,,然后复制元素,并显示它们的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
示例5:
你可以用下列方法把 shared_ptr
传递给另一个函数:
-
向
shared_ptr
传递值。 调用复制构造函数,递增引用计数,并把被调用方当做所有者。 还有就是在这次操作中有少量的开销,这很大程度上取决于你传递了多少shared_ptr
对象。 当调用方和被调用方之间的代码协定 (隐式或显式) 要求被调用方是所有者,使用此选项。 -
通过引用或常量引用来传递
shared_ptr
。 在这种情况下,引用计数不增加,并且只要调用方不超出范围,被调用方就可以访问指针。 或者,被调用方可以决定创建一个基于引用的shared_ptr
,从而成为一个共享所有者。 当调用者并不知道被被调用方,或当您必须传递一个shared_ptr
,并希望避免由于性能原因的复制操作,请使用此选项。 -
通过底层的指针或引用底层的对象。 这使得被调用方使用对象,但不使共享所有权或扩展生存期。 如果被调用方从原始指针创建一个
shared_ptr
,则新的shared_ptr
是独立于原来的,且没有控制底层的资源。 当调用方和被调用方之间的协定中明确规定调用者保留shared_ptr
生存期的所有权,则使用此选项。 -
当您决定如何传递一个
shared_ptr
时,确定被调用方是否有共享基础资源的所有权。 一个“所有者”就是只要它需要就可以使用底层资源的对象或函数。 如果调用方必须保证被调用方可以在其(函数)生存期以外扩展指针的生存期,请使用第一个选项。 如果您不关心被调用方是否扩展生存期,则通过引用传递并让被调用方复制它。 -
如果不得不允许帮助程序函数访问底层指针,并且您知道帮助程序函数将使用指针且在调用函数返回前先返回,则该函数不必共享底层指针的所有权。 仅仅是在调用方的
shared_ptr
的生存期内允许访问指针。 在这种情况下,通过引用来传递shared_ptr
,通过原始指针或引用的基本对象都是安全的。 通过此方式提供一个小的性能改进,并且还有助于表示程序的意图。 -
有时,例如在一个
std:vector<shared_ptr<T>>
中,您可能必须对传递每个shared_ptr
给lambda表达式体或命名函数对象。 如果lambda或函数没有存储指针,则通过引用传递shared_ptr
,以避免调用拷贝构造函数的每个元素。
weak_ptr
结合 shared_ptr
使用的特例智能指针。 weak_ptr
提供对一个或多个 shared_ptr
实例拥有的对象的访问,但不参与引用计数。 如果你想要观察某个对象但不需要其保持活动状态,请使用该实例。 在某些情况下,需要断开 shared_ptr
实例间的循环引用。 头文件:<memory>
。
有时对象必须存储一种方法,用来在不引起引用计数增加的情况下访问 shared_ptr
的基础对象。 通常,当您在 shared_ptr
实例之间循环引用时,就会出现此情况。
最佳的设计能够尽可能地避免指针具有共享所有权。 但是,如果您必须具有共享的 shared_ptr
实例所有权,请避免在实例之间进行循环引用。 如果循环引用不可避免,甚至由于某种原因而更为可取,请使用 weak_ptr
为一个或多个所有者提供对其他 shared_ptr
的弱引用。 使用 weak_ptr
,您可以创建连接到现有相关实例组的 shared_ptr
,但仅当基础内存资源有效时才行。 weak_ptr
本身并不参与引用计数,因此,它无法阻止引用计数转到为零。 但是,您可以使用 weak_ptr
来尝试获取 shared_ptr
的新副本,通过使用该副本进行初始化。 如果内存已被删除,则会引发 bad_weak_ptr 异常。 如果内存仍有效,则新的共享指针会递增引用计数,并确保只要 shared_ptr
变量保持在范围内,内存就有效。
示例1:
下面的代码示例演示了使用 weak_ptr
以确保正确删除循环依赖关系对象的实例。 检查示例时,假定它是仅在考虑备用解决方案后才创建的。Controller
对象表示设备处理的某个方面,并且能独立运行。 每个控制器必须能够在任何时间查询其他控制器的状态,因此,每个控制器包含私有vector<weak_ptr<Controller>>
。 由于每个向量包含一个循环引用,因此使用 weak_ptr
实例而不是 shared_ptr
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
|