进一步了解 QScopedPointer

上一篇 QScopedPointer源码解析 详细介绍了代码层次的实现,同时也留下了一个代码上的问题

隐式类型转换这样设计的原因

// Qt 封装的 QScopedPointer 提供的隐式转换代码
typedef T *QScopedPointer:: *RestrictedBool;
inline operator RestrictedBool() const
{
    return isNull() ? Q_NULLPTR : &QScopedPointer::d;
}

旧问题解读

如果有看过前2天发布的一篇翻译的话,现在这一部分代码应该是很好理解的,如果还没有看,我在重复总结一下:

  • 不直接返回 bool 类型是因为 bool 类型可以隐式转换为算术类型如整型,浮点型等,所以较易被误用
  • 所以先转换成一个中间过渡的类型,然后从这个中间类型在隐式转换成 bool 类型,所以需要找一个可以隐式转换成 bool 类型的类型
  • 根据C++标准中提到的:算术,枚举,指针,和指向成员指针的右值都可以转换成bool类型右值。又因为算数,枚举,指针类型更容易误用,所以最后选择了指向成员指针(指向成员函数指针,指向成员数据指针)
  • 又因为编译器在处理指向成员函数指针速度相对于指向成员数据指针慢一些,所以最后选择指向成员数据指针,并将该成员对象设计成私有,避免成员被访问赋值等操作

所以这一部分代码就可以这样理解了:

typedef T *QScopedPointer:: *RestrictedBool 定义了一个指向 QScopedPointer 类数据成员的指针 RestrictedBool, 该成员对象也是一个指针,类型是 T
所以operator RestrictedBool() 函数中当 isNull() 不为空的时候,返回 &QScopedPointer::d, 该指针可以隐式转换成布尔型的 true,反之,返回 nullptr, 也就是 false

C++11新标准

其实到在这里这个问题应该就算解决了,但是,我觉得代码的设计应该尽可能简洁,尽量少用一些奇技淫巧,而且我查阅资料的那篇文章 The Safe Bool Idiom 实际上是一篇2004年的文章,并且这篇文章提供了如何将一个对象转换成bool型的同时不引入一些可能存在的缺陷的技巧,我不相信 C++ 经过这么多年发展没有提供另一条简洁明了的方法。

QScopedPointer 是非引用计数的 独享资源强类型 指针,很类似于 C++11 里的 unique_ptr, 看一下它的隐式转换部分代码的实现

explicit operator bool() const noexcept
{ return get() == pointer() ? false : true; }

很好,发现了不一样的地方 explicit, 在这里使用这个关键字是 C++11 中引入的新标准, 显式的类型转换运算符,和显式的构造函数一样,编译器通常也不会将一个显示的类型转换运算符用于隐式类型转化

举个例子,看一下这个用法, demo 里构造函数没有用 explicit修饰,类型转换函数用 explicit 修饰了:

class test{
public:
    test(int i=0):val(i) {}
    explicit operator int() const { return val;}
private:
    int val;
};

测试一下具体的区别

test a = 1;                     // 构造函数并不是显式转换
int b = a + 3;                  // 编译报错,此处需要隐式类型转化,但是类型转换运算符是显式的
int c = static_cast<int> (a) + 3;  // 显式的请求类型转化

所以当类型转化运算符是显式的时候,我们必须通过显式的强制类型转化才可以,但是凡事也有例外,如果表达式被用作条件,则编译器会将显式的类型转换自动应用于它。可以具体看一下哪些例子:

  • if, while, 及 do 语句的条件部分
  • for 语句头的条件表达式
  • 逻辑非运算符(!), 逻辑或运算符(||), 逻辑与运算符(&&) 的运算对象
  • 条件运算符( ? : ) 的条件表达式

根据这个例外,也就很好的可以实现在 if 判断中可以隐式转化为布尔,代码也变得更加简洁明了了

关于智能指针

在看 QScopedPointer 源码过程中,也了解一些关于智能指针设计的思路

《More Effective C++》 条款28:Smart Pointer(智能指针)里给了一个很有意思的定义
所谓 smart pointer , 是 “看起来,用起来,感觉起来都像内建指针,但提供更多机能” 的一种对象。它们有各式各样的用途,包括资源的管理以及自动的重复写码工作。

智能指针希望可以完全替代内建指针的同时,代码可以最少范围进行改动,并且保留和内建指针相似的使用习惯

所以 QScopedPointer 在接口设计的时候,重载了 !, *, ->operator bool 来保证使用习惯的一致性
智能指针间的比较,又引入了 ==, != 的重载

一些联想

让他们看起来及感觉起来尽量想内建指针,而越接近这个目标,越会让人忘记他们正在使用智能指针

复制和赋值的问题

智能指针本身是管理一个内建指针,那就存在智能指针本身赋值和复制的问题

auto_ptr 因为其在被复制或被赋值的时候,其对指针的 所有权 会转移,在 C++11 中已经放弃 auto_ptr 转而推荐使用 unique_ptrshared_ptr

unique_ptrauto_ptr 管理内建指针都是通过所有权的形式,但是 unique_ptr 索性则禁止了复制和赋值操作
shared_ptr 则使用的是引用计数的形式

终究因为需要维护内建指针的 对象所有权, 不能让智能指针间的赋值拷贝变得更加随意

将智能指针转化为内建指针

QScopedPointer 提供了 data() 的接口,返回了内建指针

inline T *data() const
{
    return d;
}

而当允许了开发者直接使用内建指针,你是不知道开发者可能会做哪些操作的,往往会导致灾难

所以一旦一个对象使用智能指针管理后,就不该再使用原始裸指针去操作;

使用场景

在我眼里,智能指针很好的解决了内建指针管理资源的生命周期问题,当存在复杂的逻辑处理时,可能导致我忘记delete时,也许我会使用智能指针吧。

这篇就先到这里吧,接下来一周可能在整理一下 编译器类型转换 相关的知识,或者将 shared_ptr, weak_ptr 源码撸一撸?