[英]Reference to object pointed by list iterator not working
I have a container class Query_List
: 我有一个容器类
Query_List
:
template <typename Data>
class query_list
{
private:
std::mutex mx_lock;
// Underlaying container for fast read, write and acces
std::list<Data> m_DataArray;
// Index table used for fast acces over the container
std::map<uint32_t, typename std::list<Data>::iterator> m_IndexTable;
public:
query_list() { }
void Push_Back(const uint32_t& ID, const Data& Val)
{
std::lock_guard<std::mutex> _l(mx_lock);
// Add data to the container
m_DataArray.push_back(Val);
// Get iterator to the new alocated data
auto iter = m_DataArray.end();
--iter;
// Asociate ID with the index in the list
m_IndexTable[ID] = iter;
}
bool AtID(const uint32_t& ID, Data &To_Get)
{
if (!Exists(ID))
return false;
std::lock_guard<std::mutex> _l(mx_lock);
To_Get = *m_IndexTable[ID];
return true;
}
void Remove(const uint32_t& ID)
{
// Data has already been freed!
if (!Exists(ID)) return;
std::lock_guard<std::mutex> _l(mx_lock);
m_DataArray.erase(m_IndexTable[ID]);
m_IndexTable[ID] = m_DataArray.end();
}
bool Exists(const uint32_t& ID)
{
std::lock_guard<std::mutex> _l(mx_lock);
if (m_IndexTable.find(ID) == m_IndexTable.end())
return false;
if (m_IndexTable[ID] == m_DataArray.end())
return false;
return true;
}
};
The problem appears when I try to extract data from the container that is pointed by an ID: 当我尝试从ID指向的容器中提取数据时出现问题:
bool PacketManager::AppendPacket(const Packet& pk)
{
PacketQueue _queue;
// The queue is passed by reference
if (!l_ConnQueues.AtID(pk.ownerID, _queue))
return false;
// Append the packet
std::lock_guard<std::mutex> _l(_queue._mx);
size_t InitSize = _queue.OutPackets.size();
_queue.OutPackets.push(pk);
// If data is not appended to the queue
if (_queue.OutPackets.size() <= InitSize)
return false;
return true;
}
Debugging the function shows me that the data is appened to the temporary object from the queue, but not to the one from the container. 调试功能后,我发现数据是从队列中添加到临时对象的,而不是容器中的。 I suspect the cause of this behaviour to be the copy constructor of the
PackeTQueue
class. 我怀疑此行为的原因是
PackeTQueue
类的副本构造函数 。
struct PacketQueue
{
PacketQueue() { }
uint32_t ID;
std::mutex _mx;
std::queue<Packet> OutPackets;
PacketQueue& operator=(const PacketQueue& q)
{
ID = q.ID;
OutPackets = q.OutPackets;
return *this;
}
PacketQueue(const PacketQueue& queue)
{
ID = queue.ID;
OutPackets = queue.OutPackets;
}
};
My questions are: 我的问题是:
Query_List
)? Query_List
)的设计Query_List
? The problem is that your AtID()
method is returning a copy of the PacketQueue
that is stored in m_DataArray
. 问题是,你的
AtID()
方法返回的副本 PacketQueue
存储在m_DataArray
。 If you want to access the original so that you can modify it, you need to change the To_Get
output parameter to return a pointer: 如果要访问原始文件以便可以对其进行修改,则需要更改
To_Get
输出参数以返回指针:
bool AtID(uint32_t ID, Data* &To_Get)
{
std::lock_guard<std::mutex> l(mx_lock);
auto iter = m_IndexTable.find(ID);
if (iter == m_IndexTable.end())
return false;
To_Get = &*(iter->second);
return true;
}
bool PacketManager::AppendPacket(const Packet& pk)
{
PacketQueue *queue;
if (!l_ConnQueues.AtID(pk.ownerID, queue))
return false;
std::lock_guard<std::mutex> l(queue->_mx);
size_t InitSize = queue->OutPackets.size();
queue->OutPackets.push(pk);
return (queue->OutPackets.size() > InitSize);
}
Or, you can change AtID()
to return the pointer as its return value instead of using an output parameter at all: 或者,您可以更改
AtID()
以返回指针作为其返回值,而不用完全使用输出参数:
Data* AtID(uint32_t ID)
{
std::lock_guard<std::mutex> l(mx_lock);
auto iter = m_IndexTable.find(ID);
if (iter == m_IndexTable.end())
return nullptr;
return &*(iter->second);
}
bool PacketManager::AppendPacket(const Packet& pk)
{
PacketQueue *queue = l_ConnQueues.AtID(pk.ownerID);
if (!queue)
return false;
std::lock_guard<std::mutex> l(queue->_mx);
queue->OutPackets.push(pk);
return true;
}
Of course, that being said, since l_ConnQueues
's mutex is unlocked after AtID()
exits, any other thread could potentially Remove()
the PacketQueue
from the list while AppendPacket()
is still trying to push a packet into it. 当然,这样说,因为
l_ConnQueues
的互斥锁解锁之后AtID()
退出时,任何其他线程可能会 Remove()
的PacketQueue
从列表中,同时AppendPacket()
仍在试图推动一个包进去。 So, it would be safer to have AppendPacket()
keep the list's mutex locked until it is done updating the returned queue: 因此,让
AppendPacket()
保持列表的互斥锁处于锁定状态,直到完成更新返回的队列为止,这会更安全:
Data* AtID_NoLock(uint32_t ID)
{
auto iter = m_IndexTable.find(ID);
if (iter == m_IndexTable.end())
return nullptr;
return &*(iter->second);
}
Data* AtID(uint32_t ID)
{
std::lock_guard<std::mutex> l(mx_lock);
return AtID_NoLock(ID);
}
bool PacketManager::AppendPacket(const Packet& pk)
{
std::lock_guard<std::mutex> l(l_ConnQueues.mx_lock);
PacketQueue *queue = l_ConnQueues.AtID_NoLock(pk.ownerID);
if (!queue)
return false;
std::lock_guard<std::mutex> l2(queue->_mx);
queue->OutPackets.push(pk);
return true;
}
That being said, you will notice that I changed AtID()
to not use Exists()
anymore. 话虽如此,您会注意到我将
AtID()
更改为不再使用Exists()
。 There is a race condition (in Remove()
, too) that, as soon as Exists()
exits, another thread could come in and lock the mutex and alter the list
/ map
before the current thread has a chance to re-lock the mutex. 有一个竞争条件(也在
Remove()
),一旦Exists()
退出,另一个线程可能会进入并锁定互斥锁并更改list
/ map
然后当前线程才有机会重新锁定互斥体。 As such, AtID()
(and Remove()
) should not be calling Exists()
at all. 因此,
AtID()
(和Remove()
)根本不应调用Exists()
。
Also, I don't suggest having Remove()
set a stored std:list
iterator to end
, that just makes more work for AtID()
. 另外,我不建议让
Remove()
将存储的std:list
迭代器设置为end
,这只会为AtID()
更多工作。 It would be better to simply erase
the found ID
from the map
altogether: 最好完全从
map
erase
找到的ID
:
void Remove(uint32_t ID)
{
std::lock_guard<std::mutex> l(mx_lock);
auto iter = m_IndexTable.find(ID);
if (iter != m_IndexTable.end())
{
m_DataArray.erase(iter->second);
m_IndexTable.erase(iter);
}
}
Update : That being said, there is really no good reason to have a std::map
of std::list
iterators at all. 更新 :话虽如此,实际上没有充分理由完全拥有
std::list
迭代器的std::map
。 You can store your PacketQueue
objects directly in the std::map
and remove the std::list
altogether: 您可以将
PacketQueue
对象直接存储在std::map
并完全删除std::list
:
template <typename Data>
class query_list {
private:
std::mutex mx_lock;
std::map<uint32_t, Data> m_Data;
public:
query_list() { }
void Push_Back(uint32_t ID, const Data& Val) {
std::lock_guard<std::mutex> l(mx_lock);
// Add data to the container
m_Data[ID] = Val;
}
Data* AtID_NoLock(uint32_t ID) {
auto iter = m_Data.find(ID);
return (iter != m_Data.end()) ? &(iter->second) : nullptr;
}
Data* AtID(uint32_t ID) {
std::lock_guard<std::mutex> l(mx_lock);
return AtID_NoLock(ID);
}
void Remove(uint32_t ID) {
std::lock_guard<std::mutex> l(mx_lock);
auto iter = m_Data.find(ID);
if (iter != m_Data.end())
m_Data.erase(iter);
}
bool Exists(uint32_t ID) {
std::lock_guard<std::mutex> l(mx_lock);
return (m_Data.find(ID) != m_Data.end());
}
};
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.