在 Qt 中正确使用 C++11 基于范围的 for 循环

Posted

技术标签:

【中文标题】在 Qt 中正确使用 C++11 基于范围的 for 循环【英文标题】:Using C++11 range-based for loop correctly in Qt 【发布时间】:2016-03-05 06:46:25 【问题描述】:

根据this talk 在Qt 容器上使用C++11 范围基础for 时存在一定的缺陷。考虑:

QList<MyStruct> list;

for(const MyStruct &item : list)

    //...

根据谈话,陷阱来自隐式共享。在引擎盖下,基于范围的for 从容器中获取迭代器。但是因为容器不是const,所以迭代器将不是const,这显然足以让容器分离。

当您控制容器的生命周期时,这很容易解决,只需将 const 引用传递给容器以强制它使用 const_iterator 而不是分离。

QList<MyStruct> list;
const Qlist<MyStruct> &constList = list;

for(const MyStruct &item : constList)

    //...

但是,for 示例容器作为返回值呢?

QList<MyStruct> foo()  //... 

void main()

    for(const MyStruct &item : foo())
    
    

这里发生了什么?容器是否仍被复制?直觉上我会说这是为了避免可能需要这样做?

QList<MyStruct> foo()  //... 

main()
 
    for(const MyStruct &item : const_cast<const QList<MyStruct>>(foo()))
    
    

我不确定。我知道它有点冗长,但我需要它,因为我在大型容器上大量使用基于范围的 for 循环,所以谈话对我来说是正确的字符串。

到目前为止,我使用辅助函数将容器转换为 const 引用,但如果有更短/更简单的方法来实现同样的效果,我想听听。

【问题讨论】:

别担心了。所有 Qt 容器都实现了 COW 模式。在最新版本中,Qt 团队实现了对 C++11 的支持,包括移动 ctor。 顺便说一句,尝试const MyStruct&amp; const item : foo() 以 const 样式进行迭代。 @SaZ 我会试试你的建议。但是关于 COW,Qt 开发人员在链接的谈话中明确表示从容器创建非常量迭代器意味着它分离。这是有道理的,因为否则他们无法检测到您是否确实使用该迭代器来更改它,只要您能做到这一点就足够了。 我从字面上看从来没有遇到过for(const auto&amp; bla : blas) 的问题,我什至不认为这可能有问题 不应该是const QList&lt;MyStruct&gt; &amp;constList = list;而不是Qlist&lt;MyStruct&gt; &amp;constList = list;来获取const迭代器并防止分离吗?如果没有,为什么不呢? 【参考方案1】:
template<class T>
std::remove_reference_t<T> const& as_const(T&&t)return t;

可能会有所帮助。隐式共享对象返回右值可以隐式检测由于非常量迭代导致的写入共享(和分离)。

这给了你:

for(auto&&item : as_const(foo()))


它可以让您以 const 方式进行迭代(而且非常清晰)。

如果您需要引用生命周期延长才能工作,请使用 2 个重载:

template<class T>
T const as_const(T&&t)return std::forward<T>(t);
template<class T>
T const& as_const(T&t)return t;

但是迭代 const rvalues 并关心它通常是一个设计错误:它们是丢弃的副本,为什么要编辑它们呢?如果你基于 const 限定的行为非常不同,那会在其他地方咬你。

【讨论】:

事实证明,一个需要两个 as_const 重载,一个取左值 (T&) 引用(如给定),另一个取右值 (T&&) 引用(如这个例子)。当然,两者都有相同的返回值和内容。 @Resurrection 哎呀。但请注意,它可以通过上述编辑的一个重载来完成。 我希望它用于临时的原因一方面是为了完整性,另一方面是因为 Qt 中的隐式共享 (COW)。基本上制作浅拷贝既快又便宜,但是当你产生非常量迭代器时,你就执行了深拷贝。 Qt 类经常按值返回容器,因为它很便宜。但是如果你以非常量的方式迭代它们,你就可以进行深度复制。如果您不需要它,那么进行深度复制以执行 const for-range 循环将是一种浪费(有时甚至很重要)......但也许我理解错了。 :-) 非常感谢您的前瞻技巧! 较新的 Qt 版本将具有 qAsConst,请参阅 doc-snapshots.qt.io/qt5-dev/qtglobal.html#qAsConst。 为了完整起见,std::as_const是在C++17中引入的,相当于qAsConst【参考方案2】:

Qt 有一个实现来解决这个问题,qAsConst(参见https://doc.qt.io/qt-5/qtglobal.html#qAsConst)。文档说它是 Qt 的 C++17 的 std::as_const() 版本。

【讨论】:

以上是关于在 Qt 中正确使用 C++11 基于范围的 for 循环的主要内容,如果未能解决你的问题,请参考以下文章

我应该在基于 C++11 范围的 QHash::keys() 上使用 qAsConst

Qt中基于范围的循环出错

如何在基于 Qt 的应用程序中正确使用 valgrind

基于C++QT框架的地铁换乘可视化查询系统

如何让 C++0x / C++11 风格的基于范围的 for 循环与 clang 一起使用?

在 C 程序中使用 Qt 库