Qt 库与 STL 库混用的隐患
由于我在混用 Qt 与 STL 库时遇到了一些奇怪的错误.
经过检查实际上是因为两个库实现逻辑上有所冲突, 故写此文.
我会分点记录遇到的这些”冲突”, 后续有新发现持续更新.
1. Qt / C++ / C 三种风格字符串转换 ( QString::toStdString().data() )
案情
当一个方法需要传入一个 const char*
参数, 而此时数据保存在 QString
对象内时.
我最初的做法是使用 QString::toStdString().data()
先将 QString
转换为 const char*
, 再将转换后的 C 字符串传入方法.
这么做导致参数的数据丢失了.
现场大概是这个样子:
1 |
|
调查
这里检查了一下 QString::toStdString()
与 std::string::data()
两个方法的实现机制.
QString::toStdString() 的实现机制
QString::toStdString()
内部实现中, 最终构造并返回了一个 std::string
对象. 其中携带的数据也在构造时进行了拷贝. 因此在方法执行后, 这里的 Qt 字符串与 C++ 字符串二者之间不再有任何”纠葛”.
以下内容截取自 Qt 5.14.2 官方文档:
1 | std::string QString::toStdString() const |
std::string::c_str() 的实现机制
以下代码段截取自 std::string 的 STL 源码 (GCC) :
1 | // base_string.h |
注意注释部分, 此处返回的指针是指向 std::string 内部数据的指针. 实际上 _M_data()
返回的是 traits, 但最后我们还是可以得到指针. 但无论如何, 这里没有对数据进行拷贝.
PS: 顺嘴说一句, std::string::data()
与 std::string::c_str()
两种方法至少在 C++17 之前, 都是完全一致的. 二者都是调用 std::string::_M_data()
.
破案
结合两个方法的实现机制可知, 当执行 QString::toStdString().data()
时, 首先构造了一个 std::string
的临时对象, 再构造一个指向该对象内部数据的 const char*
指针并返回. 这个指针最终赋值给我所声明的那个 C 字符串.
问题的关键就在于: 这个临时的 std::string
对象本身没有被任何地方引用, 因此它将在这行语句结束之后被析构. 而析构它的同时自然也释放了其内部的数据. 此时我的这个 C 字符串实际上失去了关联, 使用其传参自然是无法获得所需数据的.
总结
根据上文可知, QString::toStdString().data()
这个转换的方式本身有问题, 关键在于转换时的 std::string
临时对象没有被引用.
我遇到的问题涉及到了方法传参, 若非要用这种方式转换, 则应该直接将该转换步骤放在参数列表中, 保证临时对象在方法执行结束之前都处于被引用的状态. 或是直接用一个 std::string
变量保存数据, 在最终需要传值的时候调用 std::string::data()
.
1 |
|
疑问
我尝试过将 QString
转为 QByteArray
, 再将 QByteArray
转为 const char*
. 这么做最后得到的 C 字符串却是拥有数据的, 但是 QByteArray::constData()
本身理应是不做深拷贝的, 具体原因暂时未知. 可能与 Qt 内部某些机制有关.
1 |
|