【问题标题】:Why does std::set::insert() call operator<() with bad parameter为什么 std::set::insert() 使用错误参数调用 operator<()
【发布时间】:2020-10-31 02:11:09
【问题描述】:

我需要帮助来理解为什么 std::set&lt;T&gt;::insert(T) 调用 operator&lt;(const T&amp;, const T&amp;) 时使用无效引用作为其第二个参数,从而在我第一次插入时创建了段错误。

这是我想在集合中插入其对象的类:

/**
 * A CPC category.
 */
class Category {

  private:
    char m_section;
    int  m_class;
    char m_subclass;
    int  m_group;
    int  m_subgroup;

  public:
    Category();
    Category(char t_section, int t_class, char t_subclass, int t_group,
             int t_subgroup);
    char get_section() const;
    int  get_class() const;
    char get_subclass() const;
    int  get_group() const;
    int  get_subgroup() const;

};

这是我的operator&lt;(抱歉,这不是最干净的方法):

/**
 * Obeys simple lexical order. A smaller-than operator for Category is
 * required to allow objects to be placed in an std::set.
 */
bool operator<(const Category& cat1, const Category& cat2)
{
    return (cat1.get_section() == cat2.get_section() ?
                cat1.get_class() == cat2.get_class() ?
                    cat1.get_subclass() == cat2.get_subclass() ?
                        cat1.get_group() == cat2.get_group() ?
                            cat1.get_subgroup() == cat2.get_subgroup() ?
                                true :
                                cat1.get_subgroup() < cat2.get_subgroup() :
                            cat1.get_group() < cat2.get_group() :
                        cat1.get_subclass() < cat2.get_subclass() :
                    cat1.get_class() < cat2.get_class() :
                cat1.get_section() < cat2.get_section());
}

这里是创建集合的代码:

std::istream& operator>> (std::istream& is, CategorySet& cs)
{
    std::set<Category>* cats;
    Category cat;
    while (is >> cat) {
        cats->insert(cat);
    }   
    cs = CategorySet{ cats };
    return is;
}

我在cats-&gt;insert(cat);之前放了一个断点:

Breakpoint 1, InnovationModelling::operator>> (is=..., cs=...) at lib/PatentData.cpp:126
126         cats->insert(cat);
(gdb) p *cats
$9 = std::set with 0 elements
(gdb) p cat
$1 = {m_section = 65 'A', m_class = 1, m_subclass = 65 'A', m_group = 1, m_subgroup = 0}
(gdb) ptype cat
type = class InnovationModelling::Category {
  private:
    char m_section;
    int m_class;
    char m_subclass;
    int m_group;
    int m_subgroup;

  public:
    Category(void);
    Category(char, int, char, int, int);
    char get_section(void) const;
    int get_class(void) const;
    char get_subclass(void) const;
    int get_group(void) const;
    int get_subgroup(void) const;
}

(上面的$9是因为我忘记表明此时集合是空的,所以我第二次运行程序。)

然后我遍历std::set的东西,直到它调用operator(),它并没有重载:

(gdb) s
std::less<InnovationModelling::Category>::operator() (this=0x5555555993c0 <std::cout@@GLIBCXX_3.4>, __x=..., __y=...)
    at /usr/include/c++/10.2.0/bits/stl_function.h:386
386       { return __x < __y; }
(gdb) p __x
$2 = (const InnovationModelling::Category &) @0x7fffffffd3b0: {m_section = 65 'A', m_class = 1, m_subclass = 65 'A', m_group = 1, 
  m_subgroup = 0}
(gdb) p __y
$3 = (const InnovationModelling::Category &) <error reading variable>

果然,operator&lt; 被这个无效引用调用,因此 get_section() 最终被调用并出现段错误:

(gdb) s
InnovationModelling::Category::get_section (this=0x26) at lib/PatentData.cpp:187
187     return m_section;
(gdb) p this
$7 = (const InnovationModelling::Category * const) 0x26
(gdb) p *this
Cannot access memory at address 0x26
(gdb) s

Program received signal SIGSEGV, Segmentation fault.
0x0000555555568412 in InnovationModelling::Category::get_section (this=0x26) at lib/PatentData.cpp:187

如果std::set::insert() 在做什么很明显,我很抱歉,但我是 C++ 的初学者,我什至无法理解它觉得有必要致电 operator&lt;()根本就是在一个空集合中插入一些东西。

【问题讨论】:

  • 我猜你的operator&lt; 有问题,很可能是它没有正确实现严格的弱排序。它也几乎不可读。请考虑使用return std::tie(cat1.a, cat1.b, etc) &lt; std::tie(cat2.a, cat2.b, etc)
  • operator&gt;&gt;() 创建一个未初始化的指针 (cats) 并反复取消引用它。这给出了未定义的行为。在取消引用它之前,它需要被初始化为指向一个有效的对象。一旦你的程序的行为未定义,所有的赌注都被取消了。包括您在 std::set misbeaving 中看到的其他功能的错觉。
  • 您的operator &lt; 在类别相同时返回true。这是一个错误。不过,可能不是导致问题的原因。
  • @Peter 这可能不是错觉,因为比较器很疯狂,但你也是对的
  • 谢谢大家。我现在看到我的问题对其他人没有帮助。我应该保留它还是删除它?我已经在没有指针的情况下重新实现了这些类,它已经解决了这个问题以及我遇到的其他问题。我不确定为什么我选择在operator&lt; 中实现预购,但我可以看到为什么它不能与&lt; 的语义一起工作,以及它如何与std::set 混淆,所以我也修复了它。

标签: c++ segmentation-fault std stdset


【解决方案1】:

这里是创建集合的代码:

'不要害怕。

您创建了一个指向集合的指针,但它实际上并不指向任何集合;它只是一个未初始化的指针。

您可以使用new 动态分配一个集合,但除非必须,否则不要这样做。事实上,除非必须,否则不要使用指针。您的需求将由CategorySet 驱动(我们对此一无所知),但如果您可以将您的 set 设置为一个不错的局部变量,那么生活会容易得多。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-21
    相关资源
    最近更新 更多