在许多人眼里,指针的好处是对相同指针类型的指针值的不同的数值表示总可以找到不同的地址对应,这样就可以在整数算术和关系操作的基础上毫无额外代价地定义指针算术和关系操作;而指针上的操作符“*”抽象的正是间接寻址操作。这就是一些用户口中的所谓“接近底层”。这种简单直接实现的最大好处就是容易以非常小的代价生成针对特定体系结构的代码。
不过因为意图不明的关系,使用指针的代码比使用其它更清晰的替代的代码更有机会错误。其中最具危害性的大概是对于存储资源管理的错误。
因为指针值并不保证编译期确定,静态检查对是否调用去配函数超过一次的检查效果很有限,要想安全使用且不泄露资源,用户必须清楚使用的指针是否可以被释放,然后准确保证从分配函数得到的非空指针值恰好作为参数调用正确的对应的去配函数一次——可是,这里是否可以释放的所有权(ownership) 信息并没有编码在指针的类型之中。
注意,一个指针有没有所有权是确定的,不存在第三种状况。然而事实是明确持有一个有所有权的指针,需要释放时,光看指针根本没法知道该使用哪个去配函数,甚至可以说,其实光从指针上根本就看不出有没有所有权。
如果一个返回指针值的函数不幸没有文档描述清楚用户该如何处理资源释放问题,就面临了两难的风险:调用错误的去配函数或重复释放导致未定义行为,或者放置不管而至少产生泄漏的后果。
可能就是因为这样, WG14 ( C 标准委员会)有一条不成文的规矩:返回指针的函数总是不带所有权——也就是用户不应该释放这里的资源。
然而就连 Austin Group (起草 POSIX 标准的作者)对此都并不买账(更别提 GNU 等了),造成了接口设计上的冲突(详见 WG14/N1174 )。可见这条本应被默认的规则在 C 用户的范畴内整体上行不通。
另外还有明明可以动态确定,却在不需要空指针的情况下不得不判断指针值是否为空,给程序运行带来不必要的开销。
其实,若需要间接操作表示资源,完全可以使用带所有权的智能指针。同时可以自动管理资源,避免资源泄漏。不加封装地使用内建指针意味着更混乱的代码路径,使其成为糟糕的代码。
若需要间接操作表示不带所有权的资源视图,使用不带所有权的特定指针类型,如 WG21/N4282 提议的 observer_ptr 来帮助表明意图。(这里使用内建指针的问题相对比较小,在没有其它选择的偷懒情况下,使用内建指针相对来说能够被容忍,因为带有所有权的指针已经被其它智能指针区分出去了。)
若需要传递引用,直接使用内建引用。在需要复制引用的场合,使用 std::ref 之类的包装。内建指针在此本质上毫无必要,并且无法使用大部分其它设施。
若需要可空类型,使用 WG21/N4480 等规范的 optional 类型。(内建指针(nullptr)仍然是个可以忍耐的替代,但并不推荐。)
若需要迭代操作,使用迭代器(iterator) 。迭代器同时有更好的类型安全性、适应性和可扩展性。指针作为随机访问迭代器的特例是可以被使用的,但仍然应当小心行事。
通过划分典型应用场景,就基本解决了上面的最麻烦的一些问题。除了静态区分存在和不存在所有权相互矛盾之外,以上类型也是可以组合的,因此同时需要多种意图也不需要使用内建指针。