对列表迭代器指向的对象不起作用的引用

Reference to object pointed by list iterator not working

本文关键字:对象 引用 不起作用 列表 迭代器      更新时间:2023-10-16

>我有一个容器类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;
}
};

当我尝试从 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;
}

调试函数显示数据已附加到队列中的临时对象,而不是附加到容器中的临时对象。我怀疑这种行为的原因是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;
}
};

我的问题是:

  • 为什么会这样?
  • 我该怎么做才能修复此错误?
  • 关于改进容器类(Query_List)的设计有什么建议吗?

问题是您的AtID()方法返回存储在m_DataArray中的PacketQueue的副本。 如果要访问原始内容以便对其进行修改,则需要更改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);
}

或者,您可以更改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;
}

当然,话虽如此,由于l_ConnQueues的互斥锁在AtID()退出后被解锁,因此任何其他线程都可能从列表中Remove()PacketQueue,而AppendPacket()仍在尝试将数据包推送到列表中。 因此,在更新返回的队列完成更新之前,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;
}

话虽如此,您会注意到我更改了AtID()不再使用Exists()。 有一个竞争条件(在Remove()中也是如此),一旦Exists()退出,另一个线程可能会进入并锁定互斥锁并更改list/map,然后当前线程有机会重新锁定互斥锁。因此,AtID()(和Remove())根本不应该称呼Exists()

另外,我不建议Remove()将存储的std:list迭代器设置为end,这只会为AtID()做更多的工作。 最好只是简单地从maperase找到的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);
}
}

更新:话虽如此,根本没有充分的理由对std::list迭代器进行std::map。您可以将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());
}
};