Вставка производных экземпляров классов в std :: set - PullRequest
2 голосов
/ 05 января 2012

Рассмотрим следующую среду:

А *

class A {
public:
A(double);
virtual bool operator==(const A&) const {return FALSE;};
virtual bool operator<(const A&) const {return FALSE;};
double[] values;
int id;
}

Bh

class B : public A {
B(double);
bool operator==(const A&) const ;
bool operator<(const A&) const;
}

C.cpp

std::set<A> myset;
for (unsigned int i = 0; i < 10; i++) {
  B tempElement = B((double)i);
  myset.insert(tempElement);
  std::cout << myset.size() << std::flush;
}

Я хочу добавить 10 элементов в мой новый набор, но выходные данные последней строки в C.cpp (1111111111) говорят мне, что в каждом из них всегда присутствует только один элементнабор.Сразу после выхода из цикла вызывается деструктор B.Как я могу предотвратить удаление объекта и его вставку в набор по требованию?Должен ли я реализовать специальный конструктор копирования или что я делаю неправильно?

РЕДАКТИРОВАТЬ: в отношении нарезки объектов: предположим, что в расширяющем классе нет новых членов.Только способ определения операторов различен ...

Ответы [ 6 ]

4 голосов
/ 05 января 2012

Проблема, с которой вы сталкиваетесь, двойственная:

  • Вы не вставляете B с в наборе, вы вставляете A с (это проблема срезов)
  • operator< неправильно определено для A

Я пропущу проблему нарезки объектов, она хорошо документирована. Если бы вы использовали чисто виртуальные функции (вместо бессмысленных определений), ваш код не скомпилировался бы, предотвращая проблему.

Теперь математически, всякий раз, когда определяется operator<, он должен определять:

Здесь ваше «фиктивное» определение operator< не может быть антисимметричным отношением, и, таким образом, алгоритмы (такие как вставка множества), которые наивно ожидают, что это будет выполнено ... неожиданно обнаружат, что дают бессвязные результаты :

Два раза меня спрашивали: «Помолитесь, мистер Бэббидж, если вы введете в машину неправильные цифры, будут ли правильные ответы?» ... Я не могу правильно понять, какое замешательство может вызвать такой вопрос.

- Чарльз Бэббидж , Отрывки из жизни философа

Я также хотел бы отметить, что фиктивное определение operator== не может быть отношением эквивалентности (а именно a == a не выполняется), однако == не используется для множества, поэтому оно не является причиной проблемы .

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

4 голосов
/ 05 января 2012

У вас проблема с нарезкой объектов здесь. A std::set сохраняет копии вставленных вами объектов, а конструктор копирования A создает объекты типа A. Таким образом, набор эффективно хранит только часть A первого вставленного вами объекта B. Вам нужно что-то вроде boost::ptr_set для хранения экземпляров производных классов.

Я не уверен, почему именно вставка не происходит (слишком мало кода), но я предполагаю, что любой объект B равен нарезанному объекту B.

0 голосов
/ 05 января 2012

Вы ошибаетесь, считая, что вы не нарезаете, потому что вы все еще создаете экземпляр A, что означает, что вызывается член базового класса для операторов <и operator ==. </p>

std:: установить считать два элемента равными, если х

0 голосов
/ 05 января 2012

Основная проблема с operator<. Используемое вами определение, которое всегда возвращает FALSE, фактически сообщает множеству, что все экземпляры A равны друг другу. В результате в набор принимается только первый A. Вы должны изменить свой A::operator< на

virtual bool operator<(const A&a) const {return this->id < a.id;};

или

virtual bool operator<(const A&a) const {return this->values[0] < a.values[0];};

или что-то в этом роде. Каковы конструкторы A и B и как инициализируются id и значения?

У вас есть другие потенциальные проблемы, такие как нарезка, упомянутая другими. Но operator< является основной проблемой на данный момент.

0 голосов
/ 05 января 2012

Хорошо, я новичок в c ++, но, как я понимаю, проблема: 1) вызов деструктора после выхода из цикла является результатом уничтожения переменной, которая существует только внутри цикла.Все переменные автоматизации имеют время жизни блока, в котором они определены. Другими словами, когда вы запускаете цикл, объект создается и память выделяется в стеке, когда вы выходите из цикла, память освобождается и так далее.деструктор называется.Чтобы избежать этого эффекта, вместо этого вы должны использовать Пуатье и динамическое распределение памяти.2) я думаю, что 1) приводит к проблемам с добавлением элемента в набор

В общем, я думаю, что вы допустили ошибку, пытаясь динамически создавать объекты с использованием нединамического выделения памяти.

Пожалуйста, исправьте меняесли я ошибаюсь.

0 голосов
/ 05 января 2012

Это не обычный случай нарезки объектов, если, как вы говорите, производный класс не имеет никаких дополнительных членов.

Рассмотрим, однако, как инициализируется vtable: вы передаете ссылку на ваш экземпляр B; это будет неявно приведено к A const &, так что набор может скопировать-построить на месте A. Это A будет A : нет способа скопировать vtable для B, поэтому он не будет вызывать B s переопределенных операторов.

См., Например, шаблон прототип , в котором для виртуального копирования используется виртуальный метод clone. Конструкторы копирования, безусловно, не обеспечивают такое поведение.

Что касается решения: храните указатели, как и все остальные.

...