简体   繁体   中英

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

I have a class that is a resource manager and keep the data in a map of < QString , void* > and the class looks like this:

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());
}

I have this struct:

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

    QString  token = "";
    qint64   lastTimeUsed = 0;

    UserData() {}
};

And in the following function I set it up:

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);
}

If I call get right after I set it up it works, but If I call it later, in another function I receive 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]

I've checked that my pointers/references to be valid, and they are and also points to the right location(the same resource manager) but I don't know why it crash, but if I call right after I call set it works.

Here how the function I call later looks like:

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

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

    // ...
}

The main problem is that your ResourceManager is too generalized. Look at its name: It manages resources . That means it needs to be able to own resources , that means to take , release and destroy the resources its supposed to manage.

Your resource manager only stores and retrieves references (here: void* pointers) to some external resources. In this form it would be better named ResourceDictionary , which implies that ownership stays outside and the user / caller is responsible to handle the lifetime of the objects.

Now, to transform what you have into a true resource manager , there is some crucial data missing: The type of the resource. Without knowing what is stored in the void * , you can't destroy (= safely delete) it.

But you want your resource manager to be templated, so I suggest this approach:

  1. Create an abstract base container, let's say struct tAbstractContainer . Beware that it needs a virtual destructor!
  2. For each type that you want to store, derive a container from that base, eg struct tQStringContainer : tAbstractContainer { QString string; }; struct tQStringContainer : tAbstractContainer { QString string; };
  3. Let your resource manager store pointers (hint: make your life easier and avoid raw pointers in favor of smart pointers) of that abstract base container.
    1. If you want raw pointers, you will need to adapt your set() function and also implement a destructor and assignment + copy (Rule of Three / Five).
    2. If you want smart pointers, choose the type of pointer wisely.

Now you can put (almost) any data into your manager and it will be correctly destroyed.

For your UserData you'd create a struct tUserDataContainer : tAbstractContainer { UserData userdata; }; struct tUserDataContainer : tAbstractContainer { UserData userdata; }; . In f() you'd create an instance of that container and fill its data accordingly, then throwing it into your manager.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM