实际上它从来没有打扰过我,因为代码编译并运行没有错误。但在阅读了一段时间的文档之后,我遇到了一个段落(引用如下)。所以我决定问知识渊博的人。
班级成员声明:
QHash<QTcpSocket*, QTime> clients;
每个套接字的信号disconnected
连接到lambda插槽:
QObject::connect(socket, &QTcpSocket::disconnected,
[this, socket]()
{
socket->disconnect();
socket->deleteLater();
this->clients.remove(socket); // <--- Removing
});
检查“睡眠”客户端:
void check_timeouts()
{
for (auto it = this->clients.cbegin(), end = this->clients.cend();
!this->clients.empty();
it = this->clients.cbegin(), end = this->clients.cend())
{
while (it != end)
{
if (it.value().elapsed() > this->client_timeout
&& it.key()->state() == QTcpSocket::ConnectedState)
{
it.key()->disconnectFromHost(); // <--- Emit disconnected inside
break;
}
++it;
}
if (it == end) // <--- Comparing
{
break;
}
}
}
正如您所看到的,如果客户端“休眠”的时间足够长,则通过调用套接字的disconnectFromHost()
来断开连接,这反过来需要执行从客户端从表中删除客户端的lambda主体。在比较时,迭代器it
和end
被视为无效。
From Qt文档:
迭代器类
迭代器提供了访问容器中项目的统一方法。 Qt的 容器类提供两种类型的迭代器:Java样式的迭代器 和STL风格的迭代器。两种类型的迭代器在时无效 容器中的数据被修改或隐式分离 由于调用非const成员函数而共享副本。
但是在这种情况下不会尝试访问容器的数据。并且在调用函数disconnectFromHost
之前,迭代器it
和end
肯定不相等,并且在调用之后立即进行比较,因为它们不相等而且它们是立即的重置为for
的 iteration_expression 中的正确值。
所以我的问题是,有可能有条件地假设在这个比较中特别是迭代器不是无效的,但是,假设是正确但过时的,这种比较是正确的而不是UB?
P.S。有一件事让我很困惑。这是迭代器比较操作是一个重载。可能会发生这样的情况:对于某些容器,事实证明迭代器的比较是通过依赖于这些迭代器的数据来访问容器来实现的。然后就会出现问题。但这些只是我的猜测。因为,据我所知,通常的迭代器使用指针操作,并且它们不需要访问容器(理论上)。
P.P.S。对不起我的英语不好。这不是我,而是在线翻译。 ; - )
修改:
所以,我找到了一个similar问题,答案完全适合我。 但我仍然想接受一个似乎可以接受的解决方法的答案。
答案 0 :(得分:1)
否即可。这显然是未定义的行为。在您持有迭代器时,容器已被修改。迭代器因修改而失效,您不能再使用它了。
解决方法很简单:在完成迭代后从哈希中删除对象。在销毁套接字之前也没有必要断开它,并且在任何情况下它已经 断开连接,因为你正在连接到它的disconnected
信号!为安全起见,如果this
指向this
,则应连接到QObject
对象上下文:
QObject::connect(socket, &QTcpSocket::disconnected, this, [this, socket]{
socket->deleteLater();
connect(socket, &QObject::destroyed, this, [this, socket]{
clients.remove(socket);
});
});
否则,指向this
的类必须保留对象上下文:
class MyClass {
QHash<QTcpSocket*, QTime> clients;
QObject context; // must be declared after clients
...
void do() {
QObject::connect(socket, &QTcpSocket::disconnected, &context, [obj = &context, socket, this]{
socket->deleteLater();
connect(socket, &QObject::destroyed, obj, [socket, this]{
clients.remove(socket);
});
});
}
};
这是为了确保在MyClass
超过套接字时自动断开连接,并且当它是悬空指针时您不会尝试访问this
。 context
充当哨兵,其存在保证允许访问this->clients
。