list iterator指向的对象引用不起作用

时间:2018-03-27 18:17:26

标签: c++ reference

我有一个容器类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)?

1 个答案:

答案 0 :(得分:0)

问题是,您的AtID()方法正在返回PacketQueue中存储的m_DataArray副本。如果要访问原始文件以便修改它,则需要更改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(),这样可以为erase做更多的工作。最好只从ID完全map找到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::map

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

t.test(Diarrhea ~ Max_by_90_H2SPos_12, data = noch4, paired=T)