четверг, 17 июня 2010 г.

STL: for_each и sort

В сети много примеров работы с STL, но часто в качестве шаблонного типа используется int или еще какой-то простой тип. Хотя довольно часто все не так тривиально и приходится хранить и сортировать достаточно сложные классы. Приведу небольшой пример. Допустим, у нас есть двухвалютный банковский счет с деньгами, причем сумма, понятно, зависит от текущего курса каждой из валют (доллар и евро). Чтобы упорядочить множество таких счетов, нужно учесть зависимость от текущего курса. Продемонстрируем это в коде:
#include <QtCore/QCoreApplication>
#include <QDebug>
#include <iostream>
#include <algorithm>
#include <list>
#include <math.h>

using namespace std;

void printInt(int numb);

//класс нашего банковского "кошелька"
class Wallet {
private:
  string owner;
  double eur;
  double usd;
public:
  //в конструкторе указываем дополнительные параметры
  //в данном случае это курсы валют относительно гривны
  Wallet(string owner, double usd, double eur) {
    this->owner = owner;
    this->eur = eur;
    this->usd = usd;
  };
  //как показывает практика, модификатор const тут необходим
  //(см. пояснения к коду)
  double getUsd() const { return usd; };
  double getEur() const { return eur; };
  void showInfo() {
    cout << "Wallet of " << owner << " contains a " << usd << " USD and " << eur << " EUR " << endl;
  };
};

void printWallet(Wallet & wallet) {
  wallet.showInfo();
};

//функтор для вывода информации
class WalletUahLess {
private:
  double uahToUsd;
  double uahToEur;
public:
  WalletUahLess(double uahToUsd, double uahToEur) {
    this->uahToUsd = uahToUsd;
    this->uahToEur = uahToEur;
  };
  //это обычный вариант сравнения двух "уошелкьков"
  bool operator() (Wallet & wallet1, Wallet & wallet2) {
    double uah1 = wallet1.getUsd()*uahToUsd + wallet1.getEur()*uahToEur;
    double uah2 = wallet2.getUsd()*uahToUsd + wallet2.getEur()*uahToEur;
    return uah1 < uah2;
  };
  //также бывает необходим и вариант с модификаторами const
  bool operator() (const Wallet & wallet1, const Wallet & wallet2) {
    double uah1 = wallet1.getUsd()*uahToUsd + wallet1.getEur()*uahToEur;
    double uah2 = wallet2.getUsd()*uahToUsd + wallet2.getEur()*uahToEur;
    return uah1 < uah2;
  };
};

int main(int argc, char *argv[])
{

  QCoreApplication a(argc, argv);

  vector<Wallet> wallets;

  //заполняем контейнер
  wallets.push_back(Wallet("Vasya", 960.50, 780.3));
  wallets.push_back(Wallet("Vova", 954.2, 789.56));
  wallets.push_back(Wallet("Piotr", 961.80, 792.67));
  wallets.push_back(Wallet("Ivan", 970.34, 760.50));
  wallets.push_back(Wallet("Sidor", 1090.76, 700.90));

  cout << endl << "before sort(): " << endl;
  for_each(wallets.begin(), wallets.end(), printWallet);

  //сортируем при одном курсе
  std::sort(wallets.begin(), wallets.end(), WalletUahLess(8.5, 9.6));
  cout << endl << "after sort() with WalletUahLess(8.5, 9.6): " << endl;
  for_each(wallets.begin(), wallets.end(), printWallet);

  //сортируем при другом курсе
  sort(wallets.begin(), wallets.end(), WalletUahLess(7.8, 11.5));
  cout << endl << "after sort() with WalletUahLess(7.8, 11.5): " << endl;
  for_each(wallets.begin(), wallets.end(), printWallet);

  return a.exec();
}

void printInt(int numb) {
  cout << numb << endl;
}

* This source code was highlighted with Source Code Highlighter.

На выходе консоли должно быть это:

before sort():
Wallet of Vasya contains a 960.5 USD and 780.3 EUR
Wallet of Vova contains a 954.2 USD and 789.56 EUR
Wallet of Piotr contains a 961.8 USD and 792.67 EUR
Wallet of Ivan contains a 970.34 USD and 760.5 EUR
Wallet of Sidor contains a 1090.76 USD and 700.9 EUR

after sort() with WalletUahLess(8.5, 9.6):
Wallet of Ivan contains a 970.34 USD and 760.5 EUR
Wallet of Vasya contains a 960.5 USD and 780.3 EUR
Wallet of Vova contains a 954.2 USD and 789.56 EUR
Wallet of Piotr contains a 961.8 USD and 792.67 EUR
Wallet of Sidor contains a 1090.76 USD and 700.9 EUR

after sort() with WalletUahLess(7.8, 11.5):
Wallet of Ivan contains a 970.34 USD and 760.5 EUR
Wallet of Vasya contains a 960.5 USD and 780.3 EUR
Wallet of Vova contains a 954.2 USD and 789.56 EUR
Wallet of Sidor contains a 1090.76 USD and 700.9 EUR
Wallet of Piotr contains a 961.8 USD and 792.67 EUR

Комментировать особо нечего, но стоит добавить, что использовать класс типа WalletUahLess вместо функции - это намного более гибкий и удобный способ сортировки, чем с помощью функции, т.к. позволяет задать дополнительные параметры - в данном случае различные курсы валют, не создавая при этом новый класс или функтор для сравнения.
В процессе выяснилась интересная вещь: на VS-2005 все замечательно работало, а на QT4 вдруг оказалось, что не хватает функции сравнения, где оба сравниваемых аргумента объявлены как const. После добавления перегруженной версии оператора (operator() (const Wallet, const Wallet &)) с константными объектами компилятор сообщил, что функции getUsd() и getEur() не могут вызываться в константном объекте, так как они сами не константные. После того, как они были объявлены константными, все заработало. Из чего можно сделать вывод, что реализации STL в разных компиляторах могут отличаться.
Также оказалось, что функция qSort() из библиоеки Qt работает так же, как и stl::sort() с объектами std::vector и не требует функции с const-параметрами.