хранение полиморфных объектов в unordered_set - PullRequest
0 голосов
/ 29 апреля 2019

Скажите, у меня есть Базовый класс

struct Base {
    int x;
};

И я знаю, что есть класс Derived, который наследуется от Base, но его определение на данный момент мне недоступно (он существует в последующем модуле).

Теперь у меня есть unordered_set<Base*>, в котором будет храниться смесь объектов Base и Derived. Если я не укажу пользовательскую хеш-функцию, будет ли этот unordered_set работать правильно (т.е. хешировать элементы по их фактическому типу, так что два объекта Derived с одинаковым x, но разными в некоторых других производных полях, будут обрабатываться как разные )

Предполагая, что мне действительно нужна пользовательская хеш-функция, поскольку у меня пока нет определения Derived, я хочу написать свою хеш-функцию как виртуальную функцию-член Base, чтобы Derived мог просто переопределите эту функцию для выполнения ее собственного хэша, когда unordered_set вызывает ее. Это возможно? Или есть какое-то другое предпочтительное / устоявшееся решение этой проблемы?

1 Ответ

1 голос
/ 29 апреля 2019

Да, вы можете сделать это следующим образом:

#include <iostream>
#include <functional>
#include <unordered_set>
#include <memory>

struct Base {
    int x;

    virtual size_t hash() const {
        return std::hash<int>()(x);
    }
};

// just some Derived example
struct Derived : Base {
    int y;

    // performing hashing for a pair of integers
    virtual size_t hash() const override {
        return std::hash<int>()(x) ^ std::hash<int>()(y);
    }
};

struct Hasher {
    size_t operator()(const Base* ptr) const {
        return ptr->hash();
    }
};

struct CheckEq {
    bool operator()(Base* lhs, Base* rhs) const {
        auto dLhs = dynamic_cast<Derived*>(lhs);
        auto dRhs = dynamic_cast<Derived*>(rhs);

        // different types
        if ((dLhs == nullptr) != (dRhs == nullptr)) {
            return false;
        }

        // both Base
        if (dLhs == nullptr && dRhs == nullptr) {
            return lhs->x == rhs->x;
        }

        // both Derived
        return dLhs->x == dRhs->x && dLhs->y == dRhs->y;
    }
};

int main() {
    std::unordered_set<
        Base*,
        Hasher,  // struct with defined size_t operator()(const Base*) const, returning hash of object
        CheckEq  // struct with defined bool operator(const Base*, const Base*) const for additional checking whether 2 objects with same hashes are different
    > set;

    // checking if it works
    auto objBase1 = std::make_unique<Base>();
    objBase1->x = 5;
    auto objBase2 = std::make_unique<Base>();
    objBase2->x = 5;
    auto objBase3 = std::make_unique<Base>();
    objBase3->x = 6;

    auto objDerived1 = std::make_unique<Derived>();
    objDerived1->x = 5;
    objDerived1->y = 6;
    auto objDerived2 = std::make_unique<Derived>();
    objDerived1->x = 5;
    objDerived1->y = 6;
    auto objDerived3 = std::make_unique<Derived>();
    objDerived1->x = 50;
    objDerived1->y = 60;

    set.insert(objBase1.get());
    set.insert(objBase2.get());
    set.insert(objBase3.get());
    set.insert(objDerived1.get());
    set.insert(objDerived2.get());
    set.insert(objDerived3.get());

    std::cout << set.size() << std::endl;  // prints 4

    return 0;
}

UPD. Мне кажется, вам нужно где-то хранить объекты, поэтому

std::unordered_set<
    std::unique_ptr<Base>,  // or shared_ptr
    Hasher,
    CheckEq
> set;

может быть, лучшая идея.

...