【问题标题】:Error inserting custom class to map as value插入自定义类以映射为值时出错
【发布时间】:2014-05-10 21:50:07
【问题描述】:

我无法理解为什么它没有像我预期的那样工作。可能是我使用的是 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.

如果我在该地图中只有一个项目,或者如果我将 affixCountChances 的定义更改为 std::unordered_map&lt;ItemRarities, ChanceSelector&lt;int&gt;*&gt;(并相应地调整其余部分),则第 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);
}

【问题讨论】:

  • 我还没有全部读完,但听起来很像你有一个错误的移动或复制构造函数。还要小心保留标识符,例如 _stuff。
  • 我已经剪掉了不相关的代码,让它变得更小。

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


【解决方案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 NumbersMagic Numbers on Wikipedia

编辑:

此外,初始化列表的内容在构造函数调用后不存在。您可能会复制一个引用而不是实际对象,然后将其删除。 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
    };

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

【讨论】:

  • 第 1 条上还可以。在 2 号上,嗯。我遗漏的所有代码看起来都与此无关。其中没有任何代码可以删除任何内容。我尝试创建一个移动构造函数和赋值运算符(它调用了它们!)但这没有帮助。
  • 添加了一些关于初始化列表的注释
  • 感谢您的周到。我已经添加了模板类的构造函数。
  • 我能够重现错误并添加信息,我如何解决错误
  • 搞砸了。听起来像是一个编译器错误,但这并不奇怪,因为我很确定直到最近才使用 MSVC 才能将初始化列表与映射一起使用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-11
  • 2019-01-21
  • 2014-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多