-1


Currently, I am testing some with Dynamic libraries and have some trouble with the Data Management/deletion... how can I 'notify' a Pointer that it got invalid?

What I need: Thread safe Way to delete Data from its library and invalidate DataContainers Ptr or a thread safe workaround.

What I tried:

Using Shared/Weak Pointer = Anyone can delete it (the library gets unloaded, but the pointer still exists in another library and deletes it there but doesn't know how.)

Possible solutions:
- Keep a list of DataContainer and set them manually to nullptr on Library Unload.
- Don't use Ptr, use Index to Vector Location and look up everytime the Data is needed.

Simple Example:

class Data
{
public:
    Data(std::string Str) : SomeStr(Str) {}
    std::string SomeStr;
};

struct DataContainer
{
    Data* m_Data = nullptr;
};

int main()
{
    // This vector is static inside a Dynamic Library so we need to use UniquePtr,
    // to be sure it gets deleted inside its Library when unloaded
    // if we could use SharedPtr/WeakPtr it would be too easy... but it could get deleted by anyone
    // Protected by Mutex
    std::vector<std::unique_ptr<Data>> DataHolder;

    DataHolder.push_back(std::make_unique<Data>("Example Str"));

    // this could maybe inside another Dynamic Library
    DataContainer Container;
    Container.m_Data = (*DataHolder.begin()).get();

    // As example instead of using a Dynamic Library that would get unloaded here
    DataHolder.clear();

    // Cannot use m_Data here, it got deleted by the DataHolder but Container don't know that
    std::cout << "Str: " << Container.m_Data->SomeStr << std::endl;

    return 0;
}

DerGaijin
  • 31
  • 3

2 Answers2

1

shared_ptr/weak_ptr is what you need. The module that keeps the ownership has shared_ptrs to objects, but it allows others to get weak_ptrs only. Other modules (who shouldn't have the ownership) have to temporary get shared_ptr out of the weak_ptr each time they need data, and they are obliged to destroy each shared_ptr immediately after they have accessed the data.

If you don't hold this invariant you need some external synchronization between modules like onPointerInvalidated, but this is a much worse design.

As for thread safety, no one can destroy the object if you keep the shared_ptr on it (unless someone does something really malicious like delete shared_ptr_.get()). That implies a contract between the consumer and the owner: consumer locks the shared_ptr for a short period of time (thus delays the destruction if any), while the owner deleting the objects doesn't worry of any dangling pointers.

Dmitry Kuzminov
  • 6,180
  • 6
  • 18
  • 40
  • Yes and it deletes the Ptr like it should... But the Ref Count Ptr is still inside the WeakPtr and also allocated by the Module that is unloaded and crashes when it tries to delete itself (Read access violation..) so its already deallocated by the Module-Unload ? – DerGaijin Nov 16 '19 at 21:33
  • @DerGaijin I'm not understanding your explanation. Be precise. – Dmitry Kuzminov Nov 17 '19 at 17:50
0

I agree, I think the shared/weak pointers should do the trick. This is how I implemented it:

class Data
{
public:
    Data(std::string Str) : SomeStr(Str) {}
    std::string SomeStr;
};

struct DataHolder {
    std::vector<std::shared_ptr<Data>> data;
    std::weak_ptr<Data> get_data(size_t idx) {
        return std::weak_ptr<Data>(data[idx]);
    }

};
struct DataContainer
{
    std::weak_ptr<Data> m_Data;
};

int main()
{
    // This vector is static inside a Dynamic Library so we need to use UniquePtr,
    // to be sure it gets deleted inside its Library when unloaded
    // if we could use SharedPtr/WeakPtr it would be too easy... but it could get deleted by anyone
    // Protected by Mutex
    DataHolder theDataHolder;

    theDataHolder.data.push_back(std::make_shared<Data>("Example Str"));


    DataContainer Container;
    Container.m_Data = theDataHolder.get_data(0);

    // container is passed to a method inside the shared lib
    auto t = shared_lib_entry(Container);

    std::this_thread::sleep_for(2s);
    // As example instead of using a Dynamic Library that would get unloaded here

    theDataHolder.data.clear();

    wait_for_completion(t);
    return 0;
}

// INSIDE the SHAERD LIB
std::thread shared_lib_entry(DataContainer &aContainer) {
    std::cout << "Entry in the lib: " << private_var << std::endl;
    std::thread aThread([&](){

            std::cout << "in the thread start" << std::endl;
            int count = 5;
            while (count-- > 0) {
                std::cout << "in the thread ["<< count <<"]" << std::endl;
                if (aContainer.m_Data.expired()) {
                    std::cout << "Someone killed the src " << std::endl;
                } else {
                    auto sp = aContainer.m_Data.lock();
                    std::cout << "Str: " << sp->SomeStr << std::endl;
                }
                std::this_thread::sleep_for(1s);
            }
    });

    std::cout << "Thread finished " << private_var << std::endl;
    return aThread;
}