【问题标题】:deleting shared_ptrs from unordered_map when deleting shared_ptr from vector ( simulatiously)从向量中删除 shared_ptr 时从 unordered_map 中删除 shared_ptr(同时)
【发布时间】:2021-11-13 12:59:39
【问题描述】:

让我们考虑这段代码:

class Organism
{ //some code here..
}
class World
{    
    unordered_map<int, std::shared_ptr<Organism>> organims_map;
    vector <std::shared_ptr<Organism>> animals_vector;
    add_organisms { 
    //i want to create specified shared_ptr destructor here

}

简而言之,我使用向量来存储 shared_pointers 来存储 World 中的所有动物。 我使用无序地图通过坐标即时访问动物(int 值将基于生物体的二维位置,我可以将其更改为 std::pair 但跳过它,现在不重要)。

我想有一个自定义删除器到 shared_ptrs 里面 std::vector 也将删除 shared_ptr 指向同一对象 std::map (因为如果我的 animals_vector 里面没有东西,它必须不要进入我的organisms_map)。

我在问如何为std::shared_ptr编写这样一个指定的析构函数。

【问题讨论】:

  • 您问错了问题,因为您专注于特定的方法。共享所有权是公平的,所有句柄都拥有该对象。您的需求不需要分享。地图应该只包含weak_ptrs。然后,您只需检查通过地图访问时对象是否消失。

标签: c++ vector smart-pointers unordered-map


【解决方案1】:

我不认为您的问题需要自定义删除器,我认为您遇到问题主要是因为您进行双重簿记。 您有一张地图和一个包含相同信息的矢量,即您世界中的一组动物。我建议你只保留地图。

您需要的是一种简单的方法来迭代地图中的所有动物,因为您可以使用自定义迭代器。这个例子向你展示了如何实现这样的语法:

int main()
{
    world_t world;
    for (const auto& animal : world.animals())
    {
        std::cout << animal << std::endl;
    }
}

完整的源代码: (PS。我仍在仔细检查自定义迭代器的完全正确性, 我不经常写它们,但对于这个例子它有效)

// https://stackoverflow.com/questions/69243916/deleting-shared-ptrs-from-unordered-map-when-deleting-shared-ptr-from-vector-s

#include <algorithm>
#include <map>
#include <string>
#include <iostream>
#include <vector>

//=================================================================================================
// map_value_iterator.h

//-------------------------------------------------------------------------------------------------
// implementation of a custom iterator over std::map that returns the values

namespace impl
{
    template<typename key_t, typename value_t>
    struct const_map_value_iterator_t
    {
        using iterator_category = std::forward_iterator_tag;
        using value_type = value_t; 
        using difference_type = std::ptrdiff_t;
        using pointer = const value_t*; // <== not completely sure this is correct, todo check
        using reference = const value_t&;

        // the underlying map iterator type
        using underlying_iterator_t = typename std::map<key_t, value_t>::const_iterator;

        const_map_value_iterator_t() = default;

        explicit const_map_value_iterator_t(const underlying_iterator_t& it) :
            m_map_iterator{ it }
        {
        }
    
        const_map_value_iterator_t(const const_map_value_iterator_t& rhs) :
            m_map_iterator{ rhs.m_map_iterator }
        {
        }
    
        const_map_value_iterator_t& operator=(const const_map_value_iterator_t& rhs)
        {
            m_map_iterator = rhs.m_map_iterator;
        }

        reference operator*() const
        {
            return (m_map_iterator->second);
        }

        pointer operator->() const
        {
            return &(m_map_iterator->second);
        }

        const_map_value_iterator_t& operator++()
        {
            ++m_map_iterator;
            return *this;
        }

        const_map_value_iterator_t& operator++(int)
        {
            ++m_map_iterator;
            return *this;
        }

        const_map_value_iterator_t& operator--()
        {
            --m_map_iterator;
            return *this;
        }

        const_map_value_iterator_t& operator--(int)
        {
            --m_map_iterator;
            return *this;
        }

        bool operator==(const const_map_value_iterator_t& rhs)
        {
            return m_map_iterator == rhs.m_map_iterator;
        }

        bool operator!=(const const_map_value_iterator_t& rhs)
        {
            return m_map_iterator != rhs.m_map_iterator;
        }

    private:
        underlying_iterator_t m_map_iterator;
    };

} /* namespace impl */

//-------------------------------------------------------------------------------------------------
// struct to initialize iterator based on a map
// and to provide a nice grouping of begin/end
// so both can be returned by one getter.

template<typename key_t, typename value_t>
struct map_values_iterator_t
{
    explicit map_values_iterator_t(const std::map<key_t, value_t>& map) :
        m_begin{ map.begin() },
        m_end{ map.end() }
    {
    }

    const auto& begin() const
    {
        return m_begin;
    };

    const auto& end() const 
    {
        return m_end;
    };

private:
    impl::const_map_value_iterator_t<key_t, value_t> m_begin;
    impl::const_map_value_iterator_t<key_t, value_t> m_end;
};

//=================================================================================================
// main.cpp
// inline example

struct animal_t
{
    animal_t(std::string n, std::size_t v) :
        name{ n },
        vigor{ v }
    {
    }

    // Sort orde by increasing vigor
    static bool order_by_vigor(const animal_t* lhs, const animal_t* rhs)
    {
        return lhs->vigor > rhs->vigor;
    }

    std::string name;
    std::size_t vigor;
};


class world_t 
{
public:

    // return the iterator over values in the map
    auto animals() 
    {
        return map_values_iterator_t{ m_map };
    }

    auto animals(bool (*sort_fn)(const animal_t* lhs, const animal_t* rhs))
    {
        // if your collection of animals is likely to change
        // then returning a sorted copy is probably more safe
        // (remove the pointer)
        // I'd rather would have used references, but they can't be sorted
        std::vector<const animal_t*> sorted_animals;

        for (const auto& animal : animals())
        {
            sorted_animals.push_back(&animal);
        }

        std::sort(sorted_animals.begin(), sorted_animals.end(), sort_fn);
        return sorted_animals;
    }

private:
    std::map<int, animal_t> m_map{ {1,{"bear",9}}, {3,{"cat",2}}, {5,{"dog",4}} };
};

//-------------------------------------------------------------------------------------------------

int main()
{
    world_t world;

    for (const auto& animal : world.animals(animal_t::order_by_vigor))
    {
        std::cout << "animal '" << animal->name << "' has vigor '" << animal->vigor << "'" << std::endl;
    }
}

【讨论】:

  • 把它放在地图里面的问题是我不能用除了它们的键之外的任何东西对元素进行排序,这就是为什么我想要它们的向量,以防我需要用活力来对动物进行排序。
  • 嘿 Michal,我已经用排序更新了示例。如果我太得意忘形就说停下来:)(我多年来一直在设计 C++ 库,并且习惯于制作易于使用但不一定易于实现的东西)。我已经更新了 animal_t 并使用排序顺序向 world_t 添加了 animals 方法
  • 感谢您的帮助:)
【解决方案2】:

您的方法行不通,因为您的自定义删除器将在最后一个 shared_ptr 到给定 Organism 消失之前执行,并且由于您将每个 Organism 保存在两个不同的数据结构中,在您从animals_vector 中删除animals_map 之后,您的animals_map 中总会有第二个shared_ptr

作为一个可行的替代方案,我建议编写自己的 remove_animal(std::shared_ptr) 方法,从两个数据结构中删除条目,并让您的代码调用该方法。 (同时创建private 两种数据结构可以帮助确保所有调用代码都使用您的方法,而不是尝试直接访问数据结构,因此您的约束不会被无意违反)

【讨论】:

  • 写的是同样的答案。绝对是这个。我可以看到这个答案的唯一改进是强调自定义删除器和析构器是两个不同的东西。以防 OP 没有意识到
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-09-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-04
  • 1970-01-01
相关资源
最近更新 更多