Поліморфне порівняння

Jun 10, 2014 22:44

Уявіть собі що у вас є контейнер вказівників на базовий клас і цей контейнер треба відсортувати за типами конкретних об’єктів що лежать у контейнері. Щоб було більше конкретно, живий приклад: маємо журнал виконання певної операції (у моєму випадку то були corporate actions: stock split, merger і spin-off, але це не важливо), треба цю операцію „відкотити“, але порядок виконання операцій при „відкочуванні“ важливий, а записи у журналі можуть іти у довільному порядку.
Проблема у написанні оператора порівняння двох об’єктів за вказівниками (або посиланнями) на базовий клас. Проблема відома під назвою „мультиметоди“ чи „подвійна диспетчеризація“. У C++ зазвичай вирішується або через матрицю відношень, або через dynamic_cast, або через (щасругнусь) паттерн „Visitor“. Але фігачити таблиці порівнянь ліниво, тому використаємо template metaprogramming voodoo щоб заставити компілятор нагенерувати то все самому.
Годі базікати, вйо до коду!


#include
#include
#include
#include
#include
#include

class Base {
public:
virtual void show() const = 0;
virtual bool less(const Base& rhs) const = 0;
};

class A : public Base {
public:
virtual void show() const { std::cout << "A\n"; }
virtual bool less(const Base& rhs) const;
};

class B : public Base {
public:
virtual void show() const { std::cout << "B\n"; }
virtual bool less(const Base& rhs) const;
};

class C : public Base {
public:
virtual void show() const { std::cout << "C\n"; }
virtual bool less(const Base& rhs) const;
};

class D : public Base {
public:
virtual void show() const { std::cout << "D\n"; }
virtual bool less(const Base& rhs) const;
};

template
bool isA(const Base& value)
{
return dynamic_cast(&value) != nullptr;
}

template
struct Compare {
template
static bool less(const U& a, const Base& b)
{
if (std::is_same::value)
return true;
else if (isA(b))
return false;
else
return Compare::template less(a, b);
}
};

template
struct Compare {
template
static bool less(const U&, const Base& b)
{
if (std::is_same::value)
return true;
else if (isA(b))
return false;
else
return false;
}
};

typedef Compare Order;

bool A::less(const Base& rhs) const { return Order::less(*this, rhs); }
bool B::less(const Base& rhs) const { return Order::less(*this, rhs); }
bool C::less(const Base& rhs) const { return Order::less(*this, rhs); }
bool D::less(const Base& rhs) const { return Order::less(*this, rhs); }

bool operator<(const std::unique_ptr& lhs, const std::unique_ptr& rhs) { return lhs->less(*rhs); }

int main()
{
std::vector> items;
items.push_back(std::unique_ptr(new C));
items.push_back(std::unique_ptr(new B));
items.push_back(std::unique_ptr(new A));
items.push_back(std::unique_ptr(new D));
std::sort(std::begin(items), std::end(items));
for (const auto& item : items)
item->show();
return 0;
}
_Winnie C++ Colorizer

$ g++ -W -Wall -Wextra -pedantic -std=c++0x main.cpp -o main
$ ./main
A
B
C
D

typedef задає порядок на множині типів. Визначити метод less для кожного типу все ж таки треба, але тіло метода буде однакове (тут невеликий трюк з виведенням типів). При компіляції з -O3 функцій isA і Compare::less у бінарнику не буде, вони цілком заінлайняться.

cpp, робота, програмування