Еще раз я перестал работать в некоторой действительно простой задаче в C++. Иногда мне жаль, что я не мог de-learn все, что я знаю от OO в Java, так как мои проблемы обычно запускаются путем размышления как Java.
Так или иначе у меня есть a std::list<BaseObject*>
то, что я хочу отсортировать. Скажем, это BaseObject
:
class BaseObject {
protected:
int id;
public:
BaseObject(int i) : id(i) {};
virtual ~BaseObject() {};
};
Я могу отсортировать список указателя на BaseObject
со структурой компаратора:
struct Comparator {
bool operator()(const BaseObject* o1, const BaseObject* o2) const {
return o1->id < o2->id;
}
};
И это было бы похоже на это:
std::list<BaseObject*> mylist;
mylist.push_back(new BaseObject(1));
mylist.push_back(new BaseObject(2));
// ...
mylist.sort(Comparator());
// intentionally omitted deletes and exception handling
До здесь, все - a-ok. Однако я представил некоторые производные классы:
class Child : public BaseObject {
protected:
int var;
public:
Child(int id1, int n) : BaseObject(id1), var(n) {};
virtual ~Child() {};
};
class GrandChild : public Child {
public:
GrandChild(int id1, int n) : Child(id1,n) {};
virtual ~GrandChild() {};
};
Таким образом, теперь я хотел бы отсортировать после следующих правил:
Child
объект c
и BaseObject
b
, b<c
BaseObject
объекты, используйте id
s, как прежде.Child
объекты, сравните var
s. Если они равны, нейтрализация к правилу 2.GrandChild
объекты должны нейтрализация к Child
поведение (правило 3).Я первоначально думал, что мог, вероятно, выполнить в некоторых бросках Comparator
. Однако это выбрасывает constness. Затем я думал, что, вероятно, мог выдержать сравнение typeid
s, но затем все выглядело грязным, и это даже не корректно.
Как я мог реализовать этот вид, все еще с помощью list<BaseObject*>::sort
?
Спасибо
Вы рассматриваете двойную отправку - то есть вызов виртуальной функции, зависящей от типов двух объектов, а не одного. Взгляните на эту статью в Википедии, чтобы получить предупреждение http://en.wikipedia.org/wiki/Double_dispatch . Я должен сказать, что всякий раз, когда я оказываюсь в такой ситуации, я пытаюсь изменить направление: -)
И могу я сделать пару замечаний по поводу вашего кода. В этом нет ничего плохого, но:
в C ++ std :: list является контейнером последней инстанции - обычно вы должны по умолчанию использовать вектор std:;, если вам специально не нужна функция, которая предоставляет только list:
защищенные данные - всегда плохая идея
Мне может не хватать чего-то базового в вопросе, похоже, вы в основном пытаетесь выполнить двухуровневую сортировку:
1-й, на основе типа класса / объекта : B
2-й, среди похожих объектов, вы хотите использовать поле id / var (за исключением GrandChild, у которого, похоже, нет такого поля).
Если это так, то есть Есть много способов снять шкуру с кошки, но почему бы не создать виртуальную функцию (например, Key () ), которую отменяют все классы?
Key () может вернуть std :: пара , первый член - это что-то, указывающее на порядок классов (возможно, такие символы, как 'b', 'c' и 'g', удобно уже в правильном порядке), второй член указывает ранг / порядок в пределах class (это будет член данных id / var внутри класса). std :: pair уже поддерживает двухуровневую сортировку.
Если это правильное понимание проблемы, может быть, вам подойдет что-то вроде , этот пример кода ?
Предоставить объекту ключ сортировки в виртуальном методе, по умолчанию это идентификатор:
class BaseObject {
protected:
int id;
public:
BaseObject(int i) : id(i) {};
virtual ~BaseObject() {};
virtual int key() const { return id; }
};
Компаратор теперь использует метод key () вместо прямого доступа к идентификатору:
struct Comparator {
bool operator()(const BaseObject* o1, const BaseObject* o2) const {
return o1->key() < o2->key();
}
};
Затем дочерний класс может переопределить это поведение и замените var
как ключ сортировки:
class Child : public BaseObject {
protected:
int var;
public:
Child(int id1, int n) : BaseObject(id1), var(n) {};
virtual ~Child() {};
int key() const { return var; }
};
Теперь ключ сортировки зависит от конкретного экземпляра, на который указывает BaseObject *, без приведения типов.
РЕДАКТИРОВАТЬ: Ой, я просто достаточно хорошо понял вашу проблему, чтобы понять, что это на самом деле не решает. См. Ответ Нила.
if (typeid(o1) == typeid(Child) && typeid(o2) == typeid(BaseObject))
return true;
if (typeid(o2) == typeid(Child) && typeid(o1) == typeid(BaseObject))
return false;
if (typeid(o1) == typeid(BaseObject) && typeid(o2) == typeid(BaseObject))
return o1-> id < o2->id;
продолжайте:)
У меня только один вопрос: важно ли иметь возможность сортировать их в этом конкретном порядке, или вам будет удобно сортировать их по типу (в любом порядке), а затем по ключу внутри типа?
class BaseObject
{
public:
static void* Category() { return typeid(BaseObject).name(); }
virtual void* category() const { return Category(); }
virtual int key() const { return mId; }
private:
int mId; // would prefer a stronger type than int...
};
bool operator<(const BaseObject& lhs, const BaseObject& rhs)
{
return lhs.category() < rhs.category()
|| (lhs.category() == rhs.category() && lhs.key() < rhs.key());
}
class ChildObject: public BaseObject
{
public:
static void* Category() { return typeid(ChildObject).name(); }
virtual void* category() const { return Category(); }
virtual int key() const { return mId; }
private:
int mVar;
};
class GrandChildObject: public ChildObject
{
};
И ] Comparator
class
struct Comparator
{
bool operator<(const BaseObject* lhs, const BaseObject* rhs) const
{
// We would not want to dereference a null pointer by mistake now would we ?
// Let's consider than a null pointer is < to a real object then :)
return lhs ? ( rhs ? *lhs < *rhs : false ) : ( rhs ? true : false );
}
};
Нет, вы не можете поместить BaseObject
раньше ... но вы можете разбить его по категориям.
class HasCategory: public std::unary_function<const BaseObject*,bool>
{
public:
explicit HasCategory(void* c): mCategory(c) {}
bool operator()(const BaseObject* b) const { return b.category() == mCategory;}
private:
void* mCategory;
};
int main(int argc, char* argv[])
{
std::vector<const BaseObject*> vec = /* */;
std::vector<const BaseObject*>::iterator it =
std::partition(vec.begin(), vec.end(), HasCategory(ChildObject::Category()));
// Now
// [ vec.begin(), it [ contains only object of category ChildObject::Category()
// [ it, vec.end() [ contains the others (whatever)
}
Единственная проблема, как уже упоминалось, заключается в том, что вы не контролируете, какая категория будет самой низкой. Для этого потребуется некоторая магия шаблонов (например), но так ли это важно?
Я вижу два подхода. Какой из них зависит от того, как вы хотите думать о проблеме (и кому принадлежит идея какого из двух объектов должен быть первым).
Если сами объекты должны иметь представление о том, как расположить друг друга, и вы почти уверены, что не собираетесь выводить больше классов с другими правилами, я бы, вероятно, добавил пару виртуальных функций в базу. класс с именами int primarySortKey () и int secondarySortKey (). Я бы использовал их в функции компаратора.
Если, с другой стороны, объекты не должны иметь представление о том, как они должны быть отсортированы (а функция компаратора должна знать гораздо больше об объектах, их значении и структуре), я, вероятно, нашел бы способ получить класс объекта в компараторе (либо посредством отражения, либо путем введения идеи типа »и напишите некоторую извращенную логику в компараторе, чтобы выяснить, что делать.