从 void* 转换为包含 QString 的结构时数据损坏

Corrupted data when converting from void* to struct that contains QString

我有一个 class,它是一个资源管理器,将数据保存在 <QString,void*> 的映射中,class 如下所示:

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]

我已经检查过我的 pointers/references 是有效的,并且它们是并且还指向正确的位置(同一个资源管理器)但我不知道它为什么会崩溃,但是如果我调用在我调用 set 之后它就起作用了。

我稍后调用的函数如下所示:

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

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

    // ...
}

主要问题是你的ResourceManager太笼统了。看它的名字:它管理资源。这意味着它需要能够拥有资源,这意味着获取释放销毁 它应该管理的资源。

您的资源管理器仅存储和检索对某些外部资源的引用(此处为:void* 指针)。在这种形式下,将其命名为 ResourceDictionary 会更好,这意味着所有权在外部,用户/调用者负责处理对象的生命周期。

现在,要将您拥有的东西转变为真正的资源管理器,缺少一些关键数据:资源的类型 .在不知道 存储在 void * 中的情况下,您无法销毁(=安全删除)它。

但是你希望你的资源管理器被模板化,所以我建议这种方法:

  1. 创建一个抽象基础容器,比方说struct tAbstractContainer。注意它需要一个虚拟析构函数!
  2. 对于您要存储的每种类型,从该基础派生一个容器,例如struct tQStringContainer : tAbstractContainer { QString string; };
  3. 让您的资源管理器存储该抽象基础容器的指针(提示:让您的生活更轻松,避免使用原始指针,而使用智能指针)。
    1. 如果您想要原始指针,您将需要调整您的 set() 函数并实现析构函数和赋值 + 复制(三/五规则)。
    2. 如果你想要智能指针,明智地选择指针类型。

现在您可以将(几乎)任何数据放入您的管理器中,这些数据将被正确销毁。

对于您的 UserData,您将创建一个 struct tUserDataContainer : tAbstractContainer { UserData userdata; };。在 f() 中,您将创建该容器的一个实例并相应地填充其数据,然后将其放入您的管理器中。