插入自定义类以映射为值时出错

时间:2014-05-10 21:50:07

标签: c++ unordered-map std-pair

我无法理解为什么这不符合我的预期。可能是我正在使用Visual Studio 2013,但是嘿。

此代码是我正在撰写的游戏引擎中项目随机化系统的一部分。

// the chance a rarity will have a given number of affixes
std::unordered_map<ItemRarities, ChanceSelector<int>> affixCountChances = {

std::pair<ItemRarities, ChanceSelector<int>>(ItemRarities::Cracked,
{ ChanceSelector<int>(
    { ChancePair(int, 100, 0) }) }),

std::pair<ItemRarities, ChanceSelector<int>>(ItemRarities::Normal,
{ ChanceSelector<int>(
    { ChancePair(int, 80, 0),
     ChancePair(int, 20, 1) }) }),

    // snip for conciseness (there are 3 more)
};

这是ChanceSelector类:

using Percentage = int;

#define ChancePair(T, p, v) std::pair<Percentage, T>(p, v)

template <class T>
class ChanceSelector
{
private:
    std::unordered_map<T, Percentage> _stuff;

public:
    ChanceSelector()
    {
    }

    ~ChanceSelector()
    {
        if (_stuff.size() > 0)
            _stuff.clear();
    }

    ChanceSelector(std::initializer_list<std::pair<Percentage, T>> list)
    {
        // snip for conciseness
    }

    T Choose()
    {
        // snip for conciseness
    }
};

上面的代码编译得很好,但我有两个问题:

  1. 我不明白为什么在std :: pair中使用ChanceSelector需要一个默认的构造函数。显然,看起来我正在使用初始化列表调用构造函数。
  2. 当应用程序运行时,它会崩溃:Unhandled exception at 0x01762fec in (my executable): 0xC0000005: Access violation reading location 0xfeeefeee.
  3. 如果我在该地图中只有一个项目,或者我将affixCountChances的定义更改为std::unordered_map<ItemRarities, ChanceSelector<int>*>(并相应地调整其余部分),则数字2消失。该错误会将此代码转储到list

    for (_Nodeptr _Pnext; _Pnode != this->_Myhead; _Pnode = _Pnext)
    {
        _Pnext = this->_Nextnode(_Pnode); // <-- this line
        this->_Freenode(_Pnode);
    }
    

    进一步检查揭示了析构函数中发生的错误。 _stuff为空:

    ~ChanceSelector()
    {
        if (_stuff.size() > 0)
        _stuff.clear();
    }
    

    它合法地称为析构函数。项目正在从_stuff中删除,但我不明白为什么它会调用析构函数。在构造完所有项目并且affixCountChances包含所有项目之后发生崩溃。我认为这意味着它正在摧毁它所创造的所有临时工,但我不明白为什么它会创造临时工。

    编辑:

    ChanceSelector的构造函数:

    ChanceSelector(std::initializer_list<std::pair<Percentage, T>> list)
    {
        int total = 0;
        int last = 100;
        for (auto& item : list)
        {
            last = item.first;
            total += item.first;
            _stuff[item.second] = total;
        }
        // total must equal 100 so that Choose always picks something
        assert(total == 100);
    }
    

1 个答案:

答案 0 :(得分:1)

回答你的两个问题:

  1. std::pair需要默认构造函数,因为您可以执行类似

    的操作
    std::pair<int, MyClass> myPair();
    

    使用默认构造函数创建类的副本(对的值是实际值而不是引用):

    // MSVC implementation
    template<class _Ty1,class _Ty2>
    struct pair 
    {   // store a pair of values
    typedef pair<_Ty1, _Ty2> _Myt;
    typedef _Ty1 first_type;
    typedef _Ty2 second_type;
    
    pair()
    : first(), second() // Here your class gets default constructed
    {     // default construct
    }
    
    // .....
    _Ty1 first; // the first stored value
    _Ty2 second;    // the second stored value
    };
    

    该对的模板完全实现,因此如果您不使用上面的行,则需要一个默认的构造函数。

    避免这种依赖性的一种方法是使用std::pair中的指针,然后将该对的第二个值的默认值设置为nullptr

    std::pair<int, MyClass*> myPair();
    
  2. 0xFEEEFEEE表示存储指针本身的存储已被删除(例如,处理已删除的类引用)。 此删除似乎发生在您在此处发布的代码之外的某处。 有关Magic Numbers的更多信息,请参阅Magic Numbers on Wikipedia

  3. 编辑:

    此外,构造函数调用后,初始化程序列表的内容不存在。您可能会复制引用而不是实际对象,然后将其删除。 std::unordered_map的msvc实现使用std::list作为存储项目的基础。我无法通过给定的代码提供有关此内容的更多信息。

    Initializer list and lifetime of its content

    编辑2:我能够使用您给定的代码重现错误,它不是initializer_list ctor的内容。 问题似乎是初始化列表中对象的生命周期。

    当我将无序映射的对的声明移出无序映射的initializer_list时,一切正常:

    std::pair<ItemRarities, ChanceSelector<int>> pair1( ItemRarities::Cracked,
        { ChanceSelector<int>(
        { ChancePair( int, 100, 0 ) } ) } );
    std::pair<ItemRarities, ChanceSelector<int>> pair2( ItemRarities::Normal,
        { ChanceSelector<int>(
        { ChancePair( int, 80, 0 ),
        ChancePair( int, 20, 1 ) } ) } );
    std::unordered_map<ItemRarities, ChanceSelector<int>> chances = {
            pair1,
            pair2
        };
    

    我不完全确定这是一个问题,但我认为它来自初始化列表中的{},当离开第一个{}时,这些对象可能会被删除在输入unordered_map的实际intializer_list之前