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

时间:2019-04-30 16:14:43

标签: c++ qt casting void-pointers

我有一个类是资源管理器,并将数据保存在<QStringvoid*>的映射中,该类如下所示:

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 :(得分:0)

主要问题是您的ResourceManager过于笼统。看一下它的名称:它管理资源。这意味着它需要能够拥有资源,这意味着获取发布 破坏 应该管理的资源。

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

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

但是您希望对资源管理器进行模板化,因此我建议采用这种方法:

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

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

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