Поліморфне порівняння
Jun. 10th, 2014 10:44 pmУявіть собі що у вас є контейнер вказівників на базовий клас і цей контейнер треба відсортувати за типами конкретних об’єктів що лежать у контейнері. Щоб було більше конкретно, живий приклад: маємо журнал виконання певної операції (у моєму випадку то були corporate actions: stock split, merger і spin-off, але це не важливо), треба цю операцію „відкотити“, але порядок виконання операцій при „відкочуванні“ важливий, а записи у журналі можуть іти у довільному порядку.
Проблема у написанні оператора порівняння двох об’єктів за вказівниками (або посиланнями) на базовий клас. Проблема відома під назвою „мультиметоди“ чи „подвійна диспетчеризація“. У C++ зазвичай вирішується або через матрицю відношень, або через dynamic_cast, або через (щасругнусь) паттерн „Visitor“. Але фігачити таблиці порівнянь ліниво, тому використаємо template metaprogramming voodoo щоб заставити компілятор нагенерувати то все самому.
Годі базікати, вйо до коду!
typedef задає порядок на множині типів. Визначити метод less для кожного типу все ж таки треба, але тіло метода буде однакове (тут невеликий трюк з виведенням типів). При компіляції з -O3 функцій isA і Compare::less у бінарнику не буде, вони цілком заінлайняться.
Проблема у написанні оператора порівняння двох об’єктів за вказівниками (або посиланнями) на базовий клас. Проблема відома під назвою „мультиметоди“ чи „подвійна диспетчеризація“. У C++ зазвичай вирішується або через матрицю відношень, або через dynamic_cast, або через (щасругнусь) паттерн „Visitor“. Але фігачити таблиці порівнянь ліниво, тому використаємо template metaprogramming voodoo щоб заставити компілятор нагенерувати то все самому.
Годі базікати, вйо до коду!
#include <iostream> #include <vector> #include <algorithm> #include <iterator> #include <memory> #include <type_traits> 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 <typename T> bool isA(const Base& value) { return dynamic_cast<const T*>(&value) != nullptr; } template <typename T, typename ... Ts> struct Compare { template <typename U> static bool less(const U& a, const Base& b) { if (std::is_same<T, U>::value) return true; else if (isA<T>(b)) return false; else return Compare<Ts ...>::template less<U>(a, b); } }; template <typename T> struct Compare<T> { template <typename U> static bool less(const U&, const Base& b) { if (std::is_same<T, U>::value) return true; else if (isA<T>(b)) return false; else return false; } }; typedef Compare<A, B, C, D> 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<Base>& lhs, const std::unique_ptr<Base>& rhs) { return lhs->less(*rhs); } int main() { std::vector<std::unique_ptr<Base>> items; items.push_back(std::unique_ptr<Base>(new C)); items.push_back(std::unique_ptr<Base>(new B)); items.push_back(std::unique_ptr<Base>(new A)); items.push_back(std::unique_ptr<Base>(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 у бінарнику не буде, вони цілком заінлайняться.