Featured image of post QScopedPointer 与 std::unique_ptr差异

QScopedPointer 与 std::unique_ptr差异

Qt 的`QScopedPointer`智能指针与 C++标准库的`std::unique_ptr`智能指针差异。

前言

通常,讨论 Qt 的智能指针时,都会与 C++标准库的智能指针比较,最常见的说法就是,QScopedPointerstd::unique_ptr 类似,QSharedPointerstd::shared_ptr 类似,但事实上,真是如此吗?

本文将讨论实际工程中遇到的一个 QScopedPointerstd::unique_ptr 的差异。

QScopedPointer

按照 Qt 官方文档的表述,QScopedPointer 通过资源获取初始化(RAII)保证当前作用域消失时,指向的对象将被删除。

QScopedPointer 实现非常简单:

 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
83
84
85
86
87
template <typename T, typename Cleanup = QScopedPointerDeleter<T> >
class QScopedPointer
{
    typedef T *QScopedPointer:: *RestrictedBool;
public:
    explicit QScopedPointer(T *p = nullptr) noexcept : d(p)
    {
    }

    inline ~QScopedPointer()
    {
        T *oldD = this->d;
        Cleanup::cleanup(oldD);
    }

    inline T &operator*() const
    {
        Q_ASSERT(d);
        return *d;
    }

    T *operator->() const noexcept
    {
        return d;
    }

    bool operator!() const noexcept
    {
        return !d;
    }

#if defined(Q_QDOC)
    inline operator bool() const
    {
        return isNull() ? nullptr : &QScopedPointer::d;
    }
#else
    operator RestrictedBool() const noexcept
    {
        return isNull() ? nullptr : &QScopedPointer::d;
    }
#endif

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

    T *get() const noexcept
    {
        return d;
    }

    bool isNull() const noexcept
    {
        return !d;
    }

    void reset(T *other = nullptr) noexcept(noexcept(Cleanup::cleanup(std::declval<T *>())))
    {
        if (d == other)
            return;
        T *oldD = d;
        d = other;
        Cleanup::cleanup(oldD);
    }

    T *take() noexcept
    {
        T *oldD = d;
        d = nullptr;
        return oldD;
    }

    void swap(QScopedPointer<T, Cleanup> &other) noexcept
    {
        qSwap(d, other.d);
    }

    typedef T *pointer;

protected:
    T *d;

private:
    Q_DISABLE_COPY(QScopedPointer)
};

简单来说,就是禁用了拷贝操作,使得成为独占指针。

QScopedPointer 与 std:: unique_ptr 的差异

相比之下,std::unique_ptr 会复杂一些,如在 C++ 14 后,增加了 std::make_unique<T> 工厂构造方法。

这里,重点讲解一个实际遇到的情形,有以下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
std::unique_ptr<QString> func1()
{
    std::unique_ptr<QString> ptr(new QString("string"));
    return ptr;
}
void func2(std::unique_ptr<QString>&& ptr)
{
    // do something, like:
    ptr->size();
}
int main()
{
    func2(func1());
    return 0;
}

这段代码,在 func1() 函数中 new 一个 unique_ptr 智能指针,然后利用 RVO(返回值优化)直接 return ptrfunc2() 函数右值引用调用 func1(),编译能通过,并且能正常运行。

但是,使用 QScopedPointer 则不同了,直接编译不过:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
QScopedPointer<QString> func1()
{
    QScopedPointer<QString> ptr(new QString("string"));
    return ptr;
}
void func2(QScopedPointer<QString>&& ptr)
{
    // do something, like:
    ptr->size();
}
int main()
{
    func2(func1());
    return 0;
}

原因在于,QScopedPointer 不能被复制或移动,但 QScopedPointer 只禁用了拷贝构造拷贝赋值,并没有显式禁用移动构造移动赋值,那为什么不能被移动呢。默认情况下,C++ 在禁用拷贝操作时不会为类自动生成移动构造函数或移动赋值操作符。

依据三五法则:

五原则:

因为用户定义(包括 = default 或 = delete)的析构函数、复制构造函数或复制赋值运算符,会阻止隐式定义移动构造函数和移动赋值运算符。

这就是为什么即使没有显式禁用移动操作,也无法通过移动语义返回或传递 QScopedPointer 对象。

总结

简单来说,QScopedPointer 可以视为阉割版的 std::unique_ptr,适用于函数范围内,不适用于函数间传递使用。若要函数间传递,则使用 std::unique_ptr