智能指针与QVector组合使用问题
智能指针与QVector组合使用问题
引言
在C++开发中,由于Qt库接口完善易用,很多桌面端程序会使用Qt各种容器库[QTL]来代替STL,关于使用QTL还是STL,萝卜白菜,各有所爱,没有绝对优劣之分;
但是可能由于Qt库成型较早缘故,其未能考虑到现代C++的演化,当Qt库与现代C++标准库组合使用时,有时会产生一些意想不到的问题;
问题由来
开发中,常常需要将对象指针序列放存放在 vector 中,比如Qt库中的QVector,且为了自动内存管理,会使用智能指针 unique_ptr 来自动管理内存 [使用 unique_ptr 而不是 shared_ptr 的原因是为了更好的区分所有权];
为了阐明问题实质,将实际开发中的复杂问题简化为下述简单示例,但是麻雀虽小,五脏俱全,问题关键在下述代码已经体现:
#include <vector>
#include <unique_ptr>
#include <QVector>
using std::vector;
using std::unique_ptr;
class CTest {
// ...
};
int main()
{
unique_ptr<CTest> ptr = std::make_unique<CTest>();
QVector<unique_ptr<CTest>> ptrVec; // 不能通过编译
// vector<unique_ptr<CTest>> ptrVec; // 可以通过编译
ptrVec.push_back(std:move(ptr));
}
上述代码中,使用 QVector 时并不能通过编译,若将 QVector 替换成 std::vector,便能通过编译,正常运行;
原因分析
当使用 QVector 时,编译器在 QVector 的 reallocData 函数中报错,详见下述代码与注释 [ 代码片段来自 Qt5.12.10 中的 qvector.h ] :
template <typename T>
void QVector<T>::reallocData(const int asize, const int aalloc, QArrayData::AllocationOptions options)
{
// ...
// QVector 隐式共享机制导致 unique_ptr 不能使用;
// 下述代码中 isShared 值为 true , 可以通过对本部分代码的调试来验证这一点;
if (isShared || !std::is_nothrow_move_constructible<T>::value) {
// we can not move the data, we need to copy construct it
while (srcBegin != srcEnd)
new (dst++) T(*srcBegin++); // 主要是本行代码需要使用 复制构造 产生问题
} else {
while (srcBegin != srcEnd)
new (dst++) T(std::move(*srcBegin++));
}
// ...
}
通过上述代码的行为可知,由于 QVector 使用隐式共享[或称写时复制(COW : copy-on-write)]机制,reallocData 函数中的 isShared 值为 true,所以需要进行复制构造操作,而 std::unique_ptr 复制构造函数无法调用,所以编译器报错,不能通过编译;
std::unique_ptr 相关部分代码如下 [ 代码片段来自 mingw7.3.0 中的 unique_ptr.h ] :
template <typename _Tp, typename _Dp = default_delete<_Tp>>
class unique_ptr {
// ...
// Disable copy from lvalue.
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
// ...
};
上述问题也是所有问题中我们最希望出现的一类,因为能在编译期发现问题,这比在运行时出现意想不到的错误要好很多,这也是编译型语言的优势所在;
最佳实践
上述问题的根源是 QVector
- 复制构造函数 =delete;
- 复制构造函数为 private;
示例代码如下:
// 不能使用 QVector<T> 的情况1 : 赋值构造函数 被删除
class Demo1 {
public:
Demo1() = default;
Demo1(const Demo1& rhs) = delete;
Demo1& operator=(const Demo1& rhs) = delete;
Demo1(Demo1&& rhs) = default;
Demo1& operator=(Demo1&& rhs) = default;
~Demo1() = default;
// ...
private:
Obj m_obj;
};
// 不能使用 QVector<T> 的情况2 : 赋值构造函数 私有化
class Demo2 {
public:
Demo2() = default;
Demo2(Demo2&& rhs) = default;
Demo2& operator=(Demo2&& rhs) = default;
~Demo2() = default;
private:
Demo2(const Demo2& rhs);
Demo2& operator=(const Demo2& rhs);
// ...
private:
Obj m_obj;
};
Qt 中 QVector
后记
后来搜索了一下,发现在 StackOverflow 中也有类似的问题:
Use std::find on QVector<std::unique_ptr
其中回答也给出了简要解释,若要更为详细的介绍,可阅读本文;