Сортировка списка указателей

еще раз я обнаружил, что не справляюсь с какой-то очень простой задачей на 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() {};
};

Итак, теперь я хотел бы сортировать следующие правила:

  1. любой Child объект c и BaseObject b, b<c
  2. сравнить BaseObject объекты, использовать ids, как и раньше.
  3. сравнить Child объекты, сравнивать его vars. Если они равны, вернитесь к правилу 2.
  4. GrandChild объекты должны отступить к Child поведение (Правило 3).

я изначально думал, что, вероятно, смогу сделать некоторые слепки в Comparator. Однако это отбрасывает Констанцию. Тогда я подумал, что, возможно, я мог бы сравнить typeidS, но тогда все выглядело грязным и это даже не правильно.

как я мог реализовать этот вид, все еще используя 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)
}

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