Сортировка списка указателей
еще раз я обнаружил, что не справляюсь с какой-то очень простой задачей на C++. Иногда мне жаль, что я не могу узнать все, что я знаю от OO на java, так как мои проблемы обычно начинаются с мышления как Java.
в любом случае, у меня есть 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
пока здесь, все в порядке. Однако я представил некоторые производные классы:
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
. Однако это отбрасывает Констанцию. Тогда я подумал, что, возможно, я мог бы сравнить typeid
S, но тогда все выглядело грязным и это даже не правильно.
как я мог реализовать этот вид, все еще используя list<BaseObject*>::sort
?
спасибо
6 ответов
вы смотрите на делаем двойной диспетчеризации - это вызов виртуальной функции в зависимости от типов двух объектов, а не один. Взгляните на эту статью Википедии для heads-up http://en.wikipedia.org/wiki/Double_dispatch. Я должен сказать, что всякий раз, когда я оказываюсь в этой ситуации, я пытаюсь изменить направление :-)
и могу ли я сделать пару замечаний о вашем коде. В этом нет ничего плохого. но:
в C++ список std::является контейнером последнего средства - обычно по умолчанию используется вектор std:;, если только вам не нужна функция, которая предоставляет только список:
защищенные данные всегда плохая идея
возможно, мне не хватает чего-то основного в вопросе, похоже, вы в основном пытаетесь сделать 2-уровневую сортировку:
1st, основанный на типе класса / объекта: B
2nd, среди подобных объектов вы хотите использовать поле id / var (за исключением внука, у которого, похоже, нет такого поля.)
Если это так, есть много способов кожи кошки, но почему бы не создать виртуальную функцию (например, Key ()), что все классы переопределяют?
Key () может вернуть std:: pair, первый член-это что-то, указывающее порядок классов (возможно, символ, такой как "b", " c " и "g", удобно уже в правильном порядке), второй член, указывающий ранг/порядок в классе (это будет член данных id/var в классе). std:: pair уже поддерживает 2-уровневую сортировку.
Если это правильно понимание проблемы, может быть, что-то вроде этот пример кода будет работать для вас?
у объекта есть ключ сортировки в виртуальном методе, по умолчанию для id:
class BaseObject {
protected:
int id;
public:
BaseObject(int i) : id(i) {};
virtual ~BaseObject() {};
virtual int key() const { return id; }
};
компаратор теперь использует метод key () вместо прямого доступа к id:
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*, без приведений.
EDIT: Ой, я просто хорошо понял вашу проблему достаточно, чтобы понять, что это на самом деле не решает. См. ответ Нила.
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;
дальше сами :)
Я вижу два подхода - какой из них зависит от того, как вы хотите думать о проблеме ( и кто владеет идеей, какой из двух объектов должен быть первым).
Если сами объекты должны иметь представление о том, как ранжировать друг друга, и вы уверены, что не собираетесь получать больше классов с разными правилами, я бы, вероятно, добавил пару виртуальных функций в базовый класс с именем "int primarySortKey ()" и " int secondarySortKey ()". Я бы использовал это в функции компаратора.
Если, с другой стороны, объекты не должны иметь представления о том, как они должны быть отсортированы ( и функция компаратора тогда должна знать намного больше об объектах, их значении и структуре), я, вероятно, найду способ получить класс объекта в компараторе ( либо через отражение, либо путем введения идеи типа " и написать некоторую искаженную логику в компараторе, чтобы выяснить, что делать.
у меня только один вопрос: важно ли иметь возможность сортировать их в этом конкретном порядке, или вы были бы в порядке с сортировкой их по типу (в любом порядке), а затем по ключу внутри типа ?
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
класс
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)
}
единственная проблема, как отмечалось, заключается в том, что вы не контролируете, какая категория будет самой низкой. Это потребует какая-то магия шаблонов (например), но так ли это важно ?