unordered_set非const迭代器

时间:2013-09-09 17:55:28

标签: c++ c++11 stl unordered-set

出于测试目的,我创建了一个小的unordered_set并尝试迭代该集合。该集合拥有自己的类:

class Student {
private:
    int matrNr;
    string name;
public:
    Student( const int& matrNr = 0, const string& name = "" )
        : matrNr( matrNr ), name( name ) {}
    void setNr( const int& matrNr ) {
        this->matrNr = matrNr;
    }
...
};

我插入了一些元素并尝试在迭代期间更改对象:

unordered_set<Student, meinHash> meineHashTable;
meineHashTable.emplace( 12, "Fred" );
meineHashTable.emplace( 22, "Barney" );
meineHashTable.emplace( 33, "Wilma" );

for (int i = 0; i < meineHashTable.bucket_count(); i++) {
    cout << "Bucketnummer: " << i << endl;
    unordered_set<Student, meinHash>::local_iterator iter;  // not constant?!?

    if (meineHashTable.bucket_size( i ) > 0) {
        for (iter = meineHashTable.begin( i ); iter != meineHashTable.end( i ); iter++) {
            //const_cast<Student&>(*iter).setNr( 1234 );  //This does work
            iter->setNr( 1234 );  //This does not work
        }

    }
    else {
        cout << "An empty Bucket" << endl;
    }

}

我使用了local_iterator(而不是const_local_iterator),但我仍然无法更改对象。由于某些原因,迭代器仍然指向一个常量对象。

我现在的问题:为什么会这样?如果普通迭代器引用const对象,那么const和非const迭代器之间有什么不同?

使用VisualStudio 2013和minGW进行测试。

提前感谢您的帮助: - )

编辑: 哈希仿函数:

struct meinHash {
    size_t operator()( const Student& s ) {
        return s.getNr();
    }
};

对于具有相同问题的未来此主题的发现者,如果您使用暴力更改matrNr,这里有一些示例输出:

const_cast<Student&>(*iter).setNr( 5 );

并尝试显示它:

unordered_set<Student, meinHash>::local_iterator iter = meineHashTable.find( 5 );
iter->display();

你可能会得到类似的东西:

  

Bucketnummer:0

     

空铲斗

     

Bucketnummer:1

     

Matrikelnummer:5

     

姓名:威尔玛

     

Bucketnummer:2

     

空铲斗

     

Bucketnummer:3

     

空铲斗

     

Bucketnummer:4

     

Matrikelnummer:5

     

姓名:弗雷德

     

Bucketnummer:5

     

空铲斗

     

Bucketnummer:6

     

Matrikelnummer:5

     

姓名:Barney

     

Bucketnummer:7

     

空铲斗

     

//不想要的输出; - )

     

Matrikelnummer:-842150451

     

姓名:

5 个答案:

答案 0 :(得分:13)

setunordered_set都有只读密钥。很容易理解为什么会出现这种情况 - 如果关键值发生变化,数据结构会将其归档到错误的位置,您将无法再找到它。

根据您的示例,假设您的哈希函数只返回matrNr字段。当哈希值更改时,1234的任何查找都将失败,因为该哈希桶中没有任何内容。

有可能更改未用于制作哈希键的对象的某些部分,但这可能导致难以追踪错误。标准委员会决定通过制作整个密钥来消除这种可能性。

这种限制有两种方法。第一种方法是从值中拆分键,然后使用mapunordered_map。第二种是从集合中删除该项目,并在其修改后重新插入。

答案 1 :(得分:7)

他们认为set<K>的类型为const Kmap<K, T>的值为pair<const K, T>;对于无序版本同样如此。

迭代器使您可以访问value_type &,并可以访问const value_type &的常量迭代器。如您所见,迭代器类型都不能“撤消”密钥的常量。

密钥是不可变的原因是它构成了底层数据结构的组成部分;更改密钥将需要一个非平凡的内部重新排列,这将导致各种问题(例如,非零计算复杂性(对于元素访问!),以及混淆迭代器排序)。

答案 2 :(得分:1)

unordered_set是一种数据结构,您无法在不改变其位置的情况下修改项目。

非const迭代器在这里是const'因为STL确实可以保护你免受这种明显的错误。

如果你想修改unordered_set的项目,你必须将其删除并重新添加。

答案 3 :(得分:1)

我有一个类似的问题,我也感到困惑。我查看过的所有消息源都表明std::unordered_set::find可以返回一个取消引用value_type&的非常量迭代器,该迭代器不是const。另一方面,以上所有答案都表明,实例中更改字段值会更改其哈希值,因此似乎无法存储哈希值。规范提供一个无法使用的接口似乎异常“草率”,因此必须有一种方法来执行类似发问者想要的事情。您只需要向编译器提供足够的信息即可知道,为您提供非常量迭代器是安全的。为了进一步简化原始问题,我们考虑以下问题:

struct student {
  std::string name;
  double gpa;

  // necessary for a decent member of a hash table. Compares all fields by default
  bool operator==(const student& other) const = default;

  student(const char* _name)
    : name(_name)
    , gpa(2.0) {}
};

std::unordered_set<student> student_set;

auto found = student_set.find("edgar"); // danger!! See note below
if (found != student_set.end()) {
  found->gpa = 4.0;  // <- compile failure here. "found" is of type const_iterator
}

如果仅使用默认的std::hash<student>,它会折叠结构中的所有数据以创建哈希-可能是std::hash<std::string>(name)std::hash<double>(gpa)的组合。不管它如何使用所有这些数据,编译器的行为都好像合并了所有数据一样,这就是其他答案所暗示的问题,即更改记录哈希的任何部分都会更改其表索引。原始问题中的unordered_set定义指定了“ MeinHash”,但我们没有看到它的含义,并且如果它包含可能通过迭代器进行更改的事物,我们将回到上面描述的问题答案。不过,通常情况下,并非记录中的所有数据都用于唯一标识集合中的实例。假设“名称”足以消除学生的歧义,而gpa只是我们可能会更新的关联数据。上面的构造函数强烈暗示着,使对find的调用变得危险。它将使用构造函数创建一个临时工,分配一个名称和gpa为2.0,然后使用这两条信息查找该学生。如果将“ edgar”添加到gpa为3.0的集合中,将永远找不到他的记录,更不用说通过迭代器上的操作进行更新了(甚至不会编译)。在推断要使用的find覆盖时,编译器会考虑迭代器的整个生命周期,因此,如果您使用的是包含结构的所有字段的朴素哈希函数,则编译器会发现您更改了其中的一个字段字段,它通过在编译时失败来“帮助”您。因此,您需要做的第一件事就是确定真正是内在字段,哈希所需的字段,而不是字段。然后,您提供仅使用这些字段的哈希函数-类似于以下内容-

struct student_hash {
   std::size_t operator()(const student& hashed_student) {
     return std::hash<std::string>()(hashed_student.name);
  }
};

对我来说(使用clang),这还不够-必要,但还不够,但是至少编译器现在知道更改“ gpa”对哈希表中记录的索引没有影响。然后,我不得不使用带有{gpa“声明的mutable关键字来明确地告诉编译器该字段可以更改,而无需更改我们编写的数据状态。通常,它用于引用计数或主指针以及不是结构实例状态所固有的其他类型的元数据,但它也适用于此。现在我们有了-

struct student {
  std::string name;
  mutable double gpa;

  // indicates that a matching name means a hit
  bool operator==(const student& other) const {
    return name.compare(other.name) == 0;
  }

  student(const char* _name)
    : name(_name)
    , gpa(2.0) {}
};

std::unordered_set<student, student_hash> student_set;

auto found = student_set.find("edgar"); // will find "edgar" regardless of gpa
if (found != student_set.end()) {
  found->gpa = 4.0;  // <- no longer fails here. "found" is of type iterator
}

答案 4 :(得分:0)

您可以将const类型转换为非const类型。这样,您就“告诉编译器”您知道自己在做什么,因此您确实应该知道自己在做什么。

相关问题