前言
通常,讨论 Qt 的智能指针时,都会与 C++标准库的智能指针比较,最常见的说法就是,QScopedPointer
与 std::unique_ptr
类似,QSharedPointer
与 std::shared_ptr
类似,但事实上,真是如此吗?
本文将讨论实际工程中遇到的一个 QScopedPointer
与 std::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 ptr
,func2()
函数右值引用调用 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
。