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

35

根据这个演讲,使用C++11范围基础的for循环处理Qt容器时存在某些陷阱。请考虑:

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)
{
    //...
}

但是对于例如将容器作为返回值的情况,该怎么办呢?

Translated:

但是对于例如将容器作为返回值的情况,该怎么办呢?

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引用,但如果有更短/更简单的方法实现相同的效果,我想听听建议。


1
别再担心这个了。所有的Qt容器都实现了COW模式。在最新版本中,Qt团队还实现了对C++11的支持,包括移动构造函数。 - Dmitry Sazonov
顺便说一下,尝试使用const MyStruct&const item:foo()以常量方式进行迭代。 - Dmitry Sazonov
1
@SaZ 我会尝试你的建议。但是关于COW,链接讨论中的Qt开发人员明确表示,从容器创建非const迭代器意味着它会分离。这很有道理,因为否则他们无法检测到您是否实际上使用了该迭代器进行更改,仅仅可以这样做就足够了。 - Resurrection
我从来没有遇到过使用 for(const auto& bla : blas) 的问题,我认为这种方式不会有任何问题。 - AngryDuck
1
应该是 const QList<MyStruct> &constList = list; 而不是 Qlist<MyStruct> &constList = list;,以获取 const 迭代器并防止分离。如果不这样做,为什么? - avb
显示剩余8条评论
2个回答

21

当我写类似 qAsConst( getStringList() ) 这样的代码时,会出现编译错误 call to deleted function。在头文件中有一行注释 // prevent rvalue arguments:\nvoid qAsConst(const T &&) = delete; - Youda008
你可能需要将getStringList()调用的结果存储到一个临时变量中,然后在该变量上调用qAsConst(或直接声明临时变量为const?) - mBardos
如果我将结果存储到一个本地变量中,那么编译器就不再警告关于分离Qt容器的问题,所以这个问题可能已经不存在了。而且,正如你所说,我已经可以将其声明为const。所以,qAsConst的存在似乎有点无意义。 - Youda008

18
template<class T>
std::remove_reference_t<T> const& as_const(T&&t){return t;}

可能会有所帮助。返回 rvalue 的隐式共享对象可以通过非 const 迭代隐式检测写共享 (并分离)。

这样就能得到:

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

这个功能可以让你以const的方式进行迭代(并且非常清晰明了)。

如果需要引用生命周期延长才能工作,则有两种重载:

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 rvalue并且关心它通常是一个设计错误:它们是一次性的副本,如果你编辑它们有什么关系呢?如果你基于const限定符表现出非常不同的行为,那在其他地方会给你带来麻烦。


@Resurrection 哎呀,不好意思。但是请注意,可以使用上面的编辑方式只用一个重载函数来完成。 - Yakk - Adam Nevraumont
2
我想要它也用于临时变量的原因一方面是为了完整性,另一方面是因为Qt中的隐式共享(COW)。基本上,浅拷贝快速且便宜,但是一旦生成非const迭代器,就会执行深拷贝。Qt类通常通过值返回容器,因为这很便宜。但是,如果以非const方式迭代它们,则会进行深拷贝。如果您不需要它,那么执行const for-range循环进行深拷贝将是一种浪费(有时还相当大)...但也许我理解错了。 :-) 无论如何,感谢您的转发技巧! - Resurrection
15
较新的Qt版本将包含qAsConst函数,详见https://doc-snapshots.qt.io/qt5-dev/qtglobal.html#qAsConst。 - Thomas McGuire
3
仅为完整性,std::as_const是在C++17中引入的,它相当于qAsConst - cbuchart
1
as_const( foo() ) 不起作用,它被声明为 void as_const(const _Tp&&) = delete; - Youda008
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接