Qt之点滴

记录Qt中的一些小方法。

1 Qt之工程配置文件(.pro)

1.1 之编译前复制需要的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 获取绝对路径
# 也可以使用 clean_path 来获取相对“干净”的路径 (去除 .. 和 .)
GSL_DIR = $$absolute_path($$PWD/../3rdParty/gsl-2.6)
INCLUDEPATH += $$GSL_DIR/include
LIBS += \
-L$$GSL_DIR/lib -lgsl \
-L$$GSL_DIR/lib -lgslcblas

# 复制动态链接库.
win32 {
# 获取路径下的所有dll文件,第二个参数如果为true:递归子目录
GSL_DLLs = $$files($$GSL_DIR/bin/*.dll, false)
for(gsl_dll, GSL_DLLs){
# windows下路径为\\,以下替换路径里的 / 为 \\
gsl_dll ~= s,/,\\,g
# copy命令,注意必须有escape_expend
cmd = copy /Y $$shell_quote($$gsl_dll) $$shell_quote(..\\Dest) $$escape_expand(\\n\\t)
QMAKE_PRE_LINK += $$cmd
# message($$cmd)
}
}
else {
# TODO: Unices
}

2 qt之编译

2.1 更新qt自带的freetype

  Qt的源码中自带的import_from_tarball.sh脚本,可用于更新Qt内含的freetype库。Qt使用的Freetype库,位于${QT_SRC}/qtbase/src/3rdparty/freetype,在该目录下有一个导入第三方库脚本:import_from_tarball.sh,可以使用msys2将新版本的freetype导入到Qt中。

  msys2命令格式,设 Qt src 位于 E:\qt\qt_build\qt-everywhere-src-5.15.1

1
2
3
# sh import_from_tarball.sh freetype_src_dir freetype_to_dir

sh import_from_tarball.sh freetype-2.10.2 /E/qt/qt_build/qt-everywhere-src-5.15.1/qtbase/src/3rdparty/freetype
  Qt编译时创建的freetype库为: ${QT_INSTALL_DIR}/lib/libqtfreetype.a

  使用时,需复制头文件(${QT_SRC}/qtbase/src/3rdparty/freetype/include)至指定的位置,此处为 Qt 安装路径下的3rdParty目录,以下内容抄自%{cmake}/share/cmake-3.18/Modules/FindFreetype.cmake文件

1
2
3
4
5
6
7
8
9
10
11
set(QT_DIR D:/Dev/Qt/5.15.1/qt)
set(FREETYPE_DIR ${QT_DIR}/../3rdParty/freetype)
set(FREETYPE_INCLUDE_DIR_freetype2 ${FREETYPE_DIR}/include)
set(FREETYPE_INCLUDE_DIR_ft2build ${FREETYPE_DIR}/include)
set(FREETYPE_LIBRARIES qtfreetype qtlibpng) # 必须加上png库

include_directories(${FREETYPE_DIR}/include)
link_directories(${QT_DIR}/lib)

# 最后别忘记链接库
target_link_libraries(${PROJ_NAME} 其他库... ${FREETYPE_LIBRARIES} )

2.2 qmake用法

1
2
3
4
# Qt 内置常量,可通过 qmake -query 查询
INCLUDEPATH += $$clean_path($$[QT_INSTALL_PREFIX]/../3rdParty/freetype/include)
LIBS += -L$$[QT_INSTALL_LIBS]
LIBS += -lqtfreetype -lqtlibpng

3 Qt容器类使用基于范围的for循环(Range-Based for Loops)

C++11规范引入了基于范围的for循环,例如对std:vector:

1
2
3
for (auto name: names) {
// ...
}

C++11编译器将上述内容翻译为:

1
2
3
4
5
6
7
for (auto name: names) {
auto && __range = names;
for (auto __begin = begin(names), __end = end(names); __begin != __end; ++__begin) {
auto&& name = *__begin;
// ...
}
}

编译器使用begin/end迭代器遍历整个names集合。

由于Qt采用copy-on-write的隐式共享特性,在Qt容器类中使用基于范围的for循环时,由于for循环内部调用begin()end()函数,会产生一个非const的容器对象从隐式共享的数据中分离出来(cause a non-const container to detach from shared data),从而出现容器类深拷贝的情况:

1
2
3
4
5
6
QDir dir;
//c++11 range-loop might detach Qt container (QStringList) [clazy-range-loop]
for (const auto &file : dir.entryList()) {
qDebug() << file;
}

解决方案很简单:

  1. 如果容器类是一个 non-const 的右值(rvalue), 定义一个const变量保存之,然后再遍历:
1
2
3
const auto strings = functionReturningQStringList();
for (const QString &s : strings)
doSomethingWith(s);
  1. 如果容器类是一个 non-const 的左值(lvalue), 先使该容器对象成为const,然后再遍历,如果无法解决,则使用std::as_const()(C++17后支持)或qAsConst():
1
2
for (const QString &s : qAsConst(container))
doSomethingWith(s);

上述处理后,没有分离,也没有非必须的"深拷贝",使性能和可读性最大化。

4 Qt之结构化绑定(Structured bindings)

Python中常用类似解绑做法:

1
2
3
# python
values = tuple(1,2)
x,y = values # 解绑

C++17开始,C++支持类似操作,称为结构化绑定:

1
2
3
QPair<QString, QString> values = {"new", "old"};
auto [x, y] = values;
qDebug()<<x<<y; // x="new",y="old"

由于与std::map的API不兼容,QMap/QHash无法使用结构化绑定实现参数解绑,问题在于Qt的迭代器仅返回值而非std中的键值对:

1
2
3
4
5
QMap<QString, QString> map = {{"hello", "no1"}, {"hi", "no2"}};
for (const auto &v : map) {
qDebug() << v;
}
// 返回: no1,no2

Qt5.10 起提供了 key_value_iteratorconst_key_value_iterator迭代器:

The QMap::key_value_iterator typedef provides an STL-style iterator for QMap and QMultiMap.

QMap::key_value_iterator is essentially the same as QMap::iterator with the difference that operator*() returns a key/value pair instead of a value.

1
2
3
4
QMap<QString, QString> map = {{"hello", "no1"}, {"hi", "no2"}};
auto [key, value] = *map.keyValueBegin();
qDebug() << key << value;
// 返回: "hello" "no1"

因此使用一个封装类做一个简单的转换,将keyValueBegin/keyValueEnd转换为begin/end,就可以使用基于范围的for循环结合结构化绑定,直接实现变量解绑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

template <typename T> class asKeyValueRange {
public:
asKeyValueRange(T &data) : m_data{data} {}
auto begin() { return m_data.keyValueBegin(); }
auto end() { return m_data.keyValueEnd(); }
private:
T &m_data; // 引用,值可修改
// const T &m_data //常引用,无法修改
};

QMap<QString, QString> map = {{"hello", "no1"}, {"hi", "no2"}};

for (auto [key, value]: asKeyValueRange(map)) {
qDebug()<<key<<value;
}
// 返回:
// "hello" "no1"
// "hi" "no2"
// QMap(("hello", "no1")("hi", "no2"))

注意,无论是否使用const关键字,key和value均为非const引用,由于T &m_dataconst,对上述值的修改,会影响原来的容器

1
2
3
4
5
6
7
for (const auto [key, value]: asKeyValueRange(map)) {
qDebug()<<key<<value;
}
// 返回:
// "hello" "hello[no1]"
// "hi" "hi[no2]"
// QMap(("hello", "hello[no1]")("hi", "hi[no2]"))

修改也很简单,将 T &m_data 定义改为 const T &m_data即可保证是常引用。

1
2
3
4
5
// 使用 const T &m_data 时:
for (auto [k, v] : asKeyValueRange(map)) {
v = k + "[" + v + "]"; // 报错
qDebug() << k << v;
}

Qt 6.5.2 中,asKeyValueRange已经成为QMap的成员函数,可以直接使用,该函数返回值仍然为非常引用,对内容的修改直接会修改原QMap容器内的值;

5 Python-like enumerate

对容器类使用enumerate同时获得索引和容器值:

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
/** **********************************************************************************
* @brief enumerate: Python-Like enumerate In C++17
* @param iterable
* @author http://reedbeta.com/blog/python-like-enumerate-in-cpp17/
************************************************************************************/
template <typename T, typename TIter = decltype(std::begin(std::declval<T>())),
typename = decltype(std::end(std::declval<T>()))>
constexpr auto enumerate(T &&iterable) {
struct iterator {
int i;
TIter iter;
bool operator!=(const iterator &other) const { return iter != other.iter; }
void operator++() {
++i;
++iter;
}
auto operator*() const { return std::tie(i, *iter); }
};
struct iterable_wrapper {
T iterable;
auto begin() { return iterator{0, std::begin(iterable)}; }
auto end() { return iterator{0, std::end(iterable)}; }
};
// return iterable_wrapper{ iterable }; // this makes a copy if iterable is a rvalue.
return iterable_wrapper{std::forward<T>(iterable)};
}
// QMap
// QMap 仅返回 value 值
QMap<QString, QString> map = {{"南京", "nanjing"}, {"北京", "beijing"}, {"上海", "shanghai"}, {"天津", "tianjin"}};

for (const auto &[idx, value] : enumerate(map)) {
qDebug() << idx << map.key(value) << value;
}
// 输出:
// 0 "上海" "shanghai"
// 1 "北京" "beijing"
// 2 "南京" "nanjing"
// 3 "天津" "tianjin"

// std::map
// 注意 std::map 中返回为std::pair
std::map<QString, QString> stdmap = {
{"南京", "std_nanjing"}, {"北京", "std_beijing"}, {"上海", "std_shanghai"}, {"天津", "std_tianjin"}};

for (const auto &[idx, value_pair] : enumerate(stdmap)) {
qDebug() << idx << value_pair.first << value_pair.second;
}
// 输出:
// 0 "上海" "std_shanghai"
// 1 "北京" "std_beijing"
// 2 "南京" "std_nanjing"
// 3 "天津" "std_tianjin"

参考

  1. Qt, range-based for loops and structured bindings(https://www.kdab.com/qt-range-based-for-loops-and-structured-bindings/)
  2. Goodbye, Q_FOREACH (https://www.kdab.com/goodbye-q_foreach/)