Поврежденные данные при преобразовании из void * в структуру, содержащую QString - PullRequest
0 голосов
/ 30 апреля 2019

У меня есть класс, который является менеджером ресурсов, и я храню данные на карте <<code>QString, void*>, и класс выглядит так:

template <typename R>
class ResourceManager
{
public:
    ResourceManager() = default;

    template <typename T>
    void set(const R& name, T& object);

    template <typename T>
    T get(const R& name);

private:
    QHash<R, void*> m_objectsMap;
};

template <typename R>
template <typename T>
void ResourceManager<R>::set(const R& name, T& object) {
    m_objectsMap.insert(name, reinterpret_cast<void*>(&object));
}

template <typename R>
template <typename T>
T ResourceManager<R>::get(const R& name) {
    auto it = m_objectsMap.find(name);

    if (it == m_objectsMap.end()) throw std::invalid_argument("The item doesn't exists");

    return *static_cast<T*>(it.value());
}

У меня есть такая структура:

struct UserData {
    QString username = "";
    QString permissions = "";

    QString  token = "";
    qint64   lastTimeUsed = 0;

    UserData() {}
};

И в следующей функции я настроил ее:

void f() {
    UserData userData;
    userData.username    = userStruct.username;
    userData.permissions = userStruct.permissions;
    userData.token       = token;
    userData.updateLastTimeUsed();

    qDebug() << "[Users][actionCheckToken]userData='" << userData.toString() << "'";

    client.getResourceManager()->set<UserData>(USER_RESOURCEMANAGER_USERDATA_KEY, userData);
}

Если я позвоню get сразу после установки, она будет работать, но если я вызову ее позже,в другой функции я получаю SIGSEGV:

1   std::__atomic_base<int>::load                  atomic_base.h       396 0x55555556500e 
2   QAtomicOps<int>::load<int>                     qatomic_cxx11.h     227 0x55555556500e 
3   QBasicAtomicInteger<int>::load                 qbasicatomic.h      103 0x555555563e5e 
4   QtPrivate::RefCount::ref                       qrefcount.h         55  0x5555555624a6 
5   QString::QString                               qstring.h           958 0x5555555629a9 
6   Users::UserData::UserData <- my struct         Users.hpp           26  0x555555578cf1 
7   ResourceManager<QString>::get<Users::UserData> ResourceManager.hpp 36  0x555555578df4 
8   [function from where I call]

Я проверил, что мои указатели / ссылки действительны, и они также указывают на правильное местоположение (тот же менеджер ресурсов), но я неНе знаю, почему происходит сбой, но если я вызываю сразу после вызова set, это работает.

Вот как выглядит функция, которую я вызываю позже:

void b(Client& client) {
    qDebug() << "[Users][userIsLogged]Called" << "clientID='" + client.getID() + "'";

    auto userData = client.getResourceManager()->get<UserData>(USER_RESOURCEMANAGER_USERDATA_KEY);

    // ...
}

1 Ответ

0 голосов
/ 02 мая 2019

Основная проблема в том, что ваш ResourceManager слишком обобщенный. Посмотрите на его имя: оно управляет ресурсами . Это означает, что он должен иметь возможность собственных ресурсов , то есть взять , освободить и уничтожить ресурсы, которыми он должен управлять.

Ваш менеджер ресурсов сохраняет и извлекает ссылки (здесь: void * указатели) на некоторые внешние ресурсы. В этой форме он будет лучше назван ResourceDictionary, что означает, что право собственности остается за пределами, а пользователь / вызывающая сторона отвечает за управление временем жизни объектов.

Теперь, чтобы преобразовать то, что у вас есть, в настоящий менеджер ресурсов , необходимо пропустить некоторые важные данные: тип ресурса. Не зная , что хранится в void *, вы не можете уничтожить (= безопасно удалить) его.

Но вы хотите, чтобы ваш менеджер ресурсов был шаблонным, поэтому я предлагаю такой подход:

  1. Создайте абстрактный базовый контейнер, скажем, struct tAbstractContainer. Остерегайтесь, что для этого нужен виртуальный деструктор!
  2. Для каждого типа, который вы хотите сохранить, выведите контейнер из этой базы, например, struct tQStringContainer : tAbstractContainer { QString string; };
  3. Пусть ваш менеджер ресурсов хранит указатели (подсказка: сделайте вашу жизнь проще и избегайте необработанных указателей в пользу умных указателей) этого абстрактного базового контейнера.
    1. Если вам нужны необработанные указатели, вам нужно будет адаптировать свою функцию set(), а также реализовать деструктор и присваивание + копирование (правило трех / пяти).
    2. Если вам нужны умные указатели, выбирайте тип указателя с умом.

Теперь вы можете поместить (почти) любые данные в свой менеджер, и они будут корректно уничтожены.

Для вашего UserData вы бы создали struct tUserDataContainer : tAbstractContainer { UserData userdata; };. В f() вы создадите экземпляр этого контейнера и соответственно заполните его данные, а затем выбросите его в свой менеджер.

...