从 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 *
中的情况下,您无法销毁(=安全删除)它。
但是你希望你的资源管理器被模板化,所以我建议这种方法:
- 创建一个抽象基础容器,比方说
struct tAbstractContainer
。注意它需要一个虚拟析构函数!
- 对于您要存储的每种类型,从该基础派生一个容器,例如
struct tQStringContainer : tAbstractContainer { QString string; };
- 让您的资源管理器存储该抽象基础容器的指针(提示:让您的生活更轻松,避免使用原始指针,而使用智能指针)。
- 如果您想要原始指针,您将需要调整您的
set()
函数并实现析构函数和赋值 + 复制(三/五规则)。
- 如果你想要智能指针,明智地选择指针类型。
现在您可以将(几乎)任何数据放入您的管理器中,这些数据将被正确销毁。
对于您的 UserData
,您将创建一个 struct tUserDataContainer : tAbstractContainer { UserData userdata; };
。在 f()
中,您将创建该容器的一个实例并相应地填充其数据,然后将其放入您的管理器中。
我有一个 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 *
中的情况下,您无法销毁(=安全删除)它。
但是你希望你的资源管理器被模板化,所以我建议这种方法:
- 创建一个抽象基础容器,比方说
struct tAbstractContainer
。注意它需要一个虚拟析构函数! - 对于您要存储的每种类型,从该基础派生一个容器,例如
struct tQStringContainer : tAbstractContainer { QString string; };
- 让您的资源管理器存储该抽象基础容器的指针(提示:让您的生活更轻松,避免使用原始指针,而使用智能指针)。
- 如果您想要原始指针,您将需要调整您的
set()
函数并实现析构函数和赋值 + 复制(三/五规则)。 - 如果你想要智能指针,明智地选择指针类型。
- 如果您想要原始指针,您将需要调整您的
现在您可以将(几乎)任何数据放入您的管理器中,这些数据将被正确销毁。
对于您的 UserData
,您将创建一个 struct tUserDataContainer : tAbstractContainer { UserData userdata; };
。在 f()
中,您将创建该容器的一个实例并相应地填充其数据,然后将其放入您的管理器中。