набор пользовательских объектов STL, каждый из которых содержит набор STL - PullRequest
4 голосов
/ 12 августа 2011

Как понятно из приведенного ниже кода, я бы хотел иметь набор объектов objectSet, каждый из которых содержит str1 и str2.Набор имеет ключ на str1, и любые новые объекты с str1, уже находящиеся в objectSet, не будут добавлены, но если этот новый объект имеет другой str2, я хочу отслеживать тот факт, что я видел его в str2Set

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <string>
#include <set>
#include <map>

using namespace std;

class Object {
public:
  string _str1;
  string _str2;
  set<string> _str2Set;

  bool operator<(const Object& b) const {
    return _str1 < b._str1;
  }
};

int main(int argc, char *argv[]) {
  set<Object> objectSet;

  Object o;
  o._str1 = "str1";
  o._str2 = "str2";

  pair< set<Object>::iterator, bool> o_ret = objectSet.insert(o);
  if (o_ret.second == false) { // key exists
    int temp = (*o_ret.first)._str2Set.size(); // this is apparently fine
    (*o_ret.first)._str2Set.insert(o._str2); // this results in the error
  }

  return 0;
}

Вот ошибка компилятора:

set_test.cpp: В функции 'int main (int, char **)': set_test.cpp: 31: ошибка: передача 'const std:: set, std :: allocator>, std :: less, std :: allocator>>, std :: allocator, std :: allocator>>> в качестве аргумента «this» для std :: pair, _Compare, typename _Alloc:: rebind <_Key> :: other> :: const_iterator, bool> std :: set <_Key, _Compare, _Alloc> :: insert (const _Key &) [с _Key = std :: basic_string, std :: allocator>, _Compare= std :: less, std :: allocator>>, _Alloc = std :: allocator, std :: allocator>>] 'отбрасывает квалификаторы

Я понимаю, что это связано с const, но все еще не могуточно выяснить, в чем проблема или как ее исправить.Просто избавиться от const не помогает.

В качестве альтернативы я попытался сохранить свои объекты в

map<string,Object> objectSet;

И, как ни странно, следующее прекрасно работает:

  pair< map<string,Object>::iterator, bool> o_ret = objectSet.insert(pair<string,Object>(o._str1,o));
  if (o_ret.second == false) { // key exists
    o_ret.first->second._str2Set.insert(o._str2);
  }

Конечно, это означаетЯ должен хранить str1 дважды, что я считаю расточительным.Спасибо за ваш вклад.

Ответы [ 5 ]

2 голосов
/ 12 августа 2011

Ошибка говорит о том, что (*o_ret.first)._str2Set является const объектом, и в результате вы не можете вызвать для него метод insert.

И это совершенно правильно: поскольку вам не разрешено изменять объекты в std::set (потому что это потенциально может нарушить согласованность контейнера), вы получаете квалифицированный const объект при разыменовании iterator в контейнер (как будто это было const_iterator).

Вы также заметили, что это работает для std::map, но это потому, что вы изменяете значение, а не ключ. Помните, что в std::set значение является ключом, поэтому его нельзя изменить.

2 голосов
/ 12 августа 2011

Ваш дизайн несовершенен.Вы используете Object как ключ к набору, но затем вы пытаетесь изменить ключи вашего набора.Конечно, вы модифицируете только те части Object, которые не влияют на его использование в качестве ключа, но компилятор этого не знает.Вы должны изменить свой дизайн, ваша вторая версия подходит мне, я не буду беспокоиться о сохранении строки дважды (обычно я не знаю ваших конкретных обстоятельств).В качестве альтернативы вы можете разделить ваш объект так, чтобы ключевая часть и часть значения были разделены.Наконец, вы можете объявить _str2set как изменяемый.

1 голос
/ 12 августа 2011

Другие ответы уже правильно сформулировали проблему с вашим подходом, вот идея для решения. Поскольку первая строка является вашим ключом, измените основную структуру данных на std::map, введенную в первую строку и содержащую оставшиеся данные в качестве полезной нагрузки:

typedef std::pair< std::string, std::set<std::string> > Payload; // or make your own class
typedef std::map<std::string, Payload>                  Collection;
typedef Collection::value_type                          Data; // this is a std::pair<string, Payload>

// A little helper to create new data objects
Data make_data(std::string s1, std::string s2)
{
  return Data(s1, Payload(s2, std::set<std::string>()));
}

Collection m;

Data x = make_data("mystr1", "mystr2");

std::pair<Collection::iterator, bool> res = m.insert(x);
if (res.second == false)
{
  Payload & p = *res.first;
  p.second.insert(x.second.first);
}

В некотором смысле вторая строка немного избыточна, и вы можете изменить ее, чтобы полностью избавиться от нее: вместо insert используйте find, и если ключ уже существует, вы добавляете свою вторую Строка к набору.

1 голос
/ 12 августа 2011

Итератор, который вы получаете после вставки набора, имеет константный итератор для первого члена пары - вы не должны изменять объект, который вы вставили в набор, потому что плохие вещи произойдут, если изменение повлияет на порядок , Так что вызывая size, это нормально, потому что это метод const, но insert отсутствует, потому что он изменяет объект в наборе. Карта работает, потому что объект - это значение, а не ключ, поэтому вы можете изменить его, не влияя на индексирование карты (строка копируется, когда вы вставляли карту).

Если вы хотите использовать набор для хранения своих Объектов и избежать дополнительной копии строки, способ обновить ее - это удалить Объект из набора (сделать копию перед его удалением), обновить копию и затем снова вставьте копию. Вы не можете обновить его, пока оно в наборе. В данный момент я удален от своих ссылок на STL, поэтому не могу дать вам код, но это общая идея для подражания.

0 голосов
/ 16 августа 2011

Прочитав все полезные комментарии и узнав намного больше, чем я подумал Я когда-либо хотел узнать о мелкой константности и изменчивости, я понял, что могу достичь всего, что хотел, просто сохранив указатель на _str2set.Лично я считаю, что объявить его изменчивым, как предлагает @john, хорошо, но, возможно, некоторые люди сочтут решение ptr менее нежелательным.

class Object {
public:
  string _str1;
  string _str2;
  set<string> * _str2Set;

  bool operator<(const Object& b) const {
    return _str1 < b._str1;
  }
};

int main(int argc, char *argv[]) {
  set<Object> objectSet;

  Object o;
  o._str1 = "str1";
  o._str2 = "str2";
  o._str2Set = new (set<string>);

  pair< set<Object>::iterator, bool> o_ret = objectSet.insert(o);
  if (o_ret.second == false) { // key exists
    (*o_ret.first)._str2Set->insert(o._str2); // this results in the error
    cout << (*o_ret.first)._str2Set->size() << endl;
  }
  return 0;
}
...