简体   繁体   English

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

[英]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: 我的问题是:

  • Why is this happening? 为什么会这样呢?
  • What can I do to fix this error? 我该如何解决该错误?
  • Any suggestions on improving the design of the container class ( 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM