Как я могу создать безопасный дескриптор, который ведет себя почти так же, как int64_t, но запрещает неявные преобразования между типами дескрипторов? - PullRequest
0 голосов
/ 11 июля 2020

Контекст:

Я создал общий c контейнер под названием «ComponentManager». Это выглядит примерно так:

typedef uint64_t handle;

template<typename T>
class ComponentManager {
    std::vector<T> components; // stores the elements themselves
    std::unordered_map<handle, unsigned int> handles; // maps handles to indices
    HandleGenerator handleGenerator; // class that generates unique handles
public:
    size_t size() const;

    handle add(T component); // Adds an element to the component manager, and returns the handle for that element

    T* find(handle key) const; // returns a pointer to the element refered to by this handle, or NULL if none exists

    void remove(handle key); // may invalidate all iterators. Does NOT invalidate any key (except the one to the element deleted)

    ComponentManagerIterator<T> begin();
    ComponentManagerIterator<T> end();
};

Этот класс использует ha sh карту дескрипторов -> индексы, чтобы разрешить произвольный доступ O (1) к элементам, а также обеспечить эффективную итерацию по элементам в кэше. из-за их пространственного расположения в векторе (хотя не гарантируется порядок c, поскольку элементы могут перемещаться при удалении элементов)

Проблема:

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

Я хотел бы смягчить это, предоставив сами ручки разного типа, в зависимости от типа handlemanager. Что-то похожее на это:

template<typename T>
class handle
{
public:
    uint64_t key;
    explicit handle(uint64_t key_) : key(key_) {}
}

Чтобы, если вы попытаетесь использовать дескриптор для неправильного типа handlemanager, код не будет компилироваться без явного приведения.

Однако проблема при этом я все еще иногда хочу рассматривать эти дескрипторы как целые числа. Я бы хотел, чтобы для этого типа были определены все обычные целочисленные операции (сравнение, побитовые операции, et c), и я бы хотел, чтобы алгоритмы, специализированные для целых чисел (например, std :: ha sh), работали так, как если бы мой тип был целым числом. Есть ли способ сделать это, не выполняя вручную каждую из этих операций?

Если есть лучший способ добиться безопасности типов, подобный этому, другим способом, я открыт для других способов достижения этого.

РЕДАКТИРОВАТЬ: Я также должен упомянуть, что в моей программе всегда будет только один диспетчер компонентов для любого заданного типа T, поэтому одного типа будет достаточно, чтобы идентифицировать конкретный c диспетчер компонентов.

РЕДАКТИРОВАТЬ 2 (дополнительный контекст): одно преимущество, которое я вижу в предоставлении уникальных типов дескрипторов, заключается в том, что я могу перегрузить одну функцию для доступа к различным менеджерам компонентов в зависимости от типа дескриптора.

1 Ответ

1 голос
/ 11 июля 2020

Вы можете создать метод для выполнения неявных преобразований из handle в uint64_t.

template<typename T>
class handle
{
public:
    operator uint64_t() { return key_; }
    explicit handle(uint64_t key) : key_(key) {}
private:
    uint64_t key_;
}

Это автоматически преобразует handle<T> в uint64_t, когда контекст вызывает его .

#include <iostream>
#include <string>

template<typename T>
class handle
{
public:
    operator uint64_t() { return key_; }
    explicit handle(uint64_t key) : key_(key) {}
private:
    uint64_t key_;
};

template<typename T>
int plus_20(T t)
{
    return t + 20;
}

int main()
{
  handle<int> hand(4);
  std::cout << hand << std::endl; // 4
  std::cout << hand + 1 << std::endl; // 5
  std::cout << (hand << 3) << std::endl; // 32
  std::cout << plus_20(hand) << std::endl; // 24
  //std::cout << plus_20<std::string>(hand) << std::endl; // doesn't compile

  std::unordered_map<uint64_t, std::string> umap;
  umap[hand] = "test";
  for(auto [key, value] : umap)
  {
      std::cout << key << " --> " << value << std::endl;
  }
}

Теперь ваш класс может выглядеть следующим образом (пропуская неизмененные части):

template<typename T>
class ComponentManager {
    // ...
    std::unordered_map<uint64_t, unsigned int> handles; // maps handles to indices
    // ...
public:
    // ...
    handle<T> add(T component); // Adds an element to the component manager, and returns the handle for that element

    T* find(handle<T> key) const; // returns a pointer to the element refered to by this handle, or NULL if none exists

    void remove(handle<T> key); // may invalidate all iterators. Does NOT invalidate any key (except the one to the element deleted)
    // ...
};

Обратите внимание, что std::unordered_map в классе ComponentManager принимает uint64_t как его ключ. Это параметры и возвращаемые значения для методов publi c add(), find() и remove(), которые обеспечивают безопасность типов. Обозначение explicit в конструкторе handle<T> выполняет много работы, чтобы гарантировать, что один вид дескриптора не может быть неявно преобразован в другой.

...