Hello,

I was running some tests involving std::weak_ptr using boost serialization, and I realized that when I read a std::weak_ptr from an archive that doesn't contain the "parent" std::share_ptr, the behavior of the read pointer is "hard to understand".

Here is an example :

#include <cassert>

#include <sstream>
#include <memory>

#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>

#include <boost/serialization/export.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/weak_ptr.hpp>

class MyInt
{
public:
    MyInt()
        : value(0)
    {
    }

    int value;

    template<class Archive>
    void serialize(Archive &ar, unsigned int)
    {
        ar & value;
    }
};

int main()
{
    std::stringstream data;

    {
        std::shared_ptr<MyInt> shared = std::make_shared<MyInt>();
        assert(shared.use_count() == 1);
        shared->value = 12;

        /* Write a weak_ptr */
        {
            std::weak_ptr<MyInt> weak(shared);
            assert(shared.use_count() == 1);

            boost::archive::binary_oarchive ar(data);

            ar << weak;
            assert(shared.use_count() == 1);
        }

        assert(shared.use_count() == 1);

        /* Read a weak_ptr */
        std::weak_ptr<MyInt> weak;
        {
            boost::archive::binary_iarchive ar(data);

            ar >> weak;

            assert(shared.use_count() == 1);
            assert(weak.use_count() == 1);

            {
                std::shared_ptr<MyInt> loaded = weak.lock();
                assert(loaded);

                /* It looks good */
                assert(loaded->value == 12);

                /* Let's see... */
                assert(loaded != shared);
                shared->value = 42;
                assert(loaded->value == 12);

                /* So another shared_ptr exists somewhere... */
                assert(shared.use_count() == 1);
                assert(loaded.use_count() == 2);
                assert(weak.use_count() == 2);
            }

            /* But where ? */
            assert(shared.use_count() == 1);
            assert(weak.use_count() == 1);
        }

        /* Let's see */
        assert(shared.use_count() == 1);
        assert(weak.use_count() == 0 /* weak.expired() */);

        /* Hmm, was it inside the archive itself ? */
    }

    /* Let's try again, we need the input stream to be back to the beginning */
    data.seekg(0);

    std::weak_ptr<MyInt> weak;
    {
        boost::archive::binary_iarchive ar(data);

        assert(weak.use_count() == 0);

        ar >> weak;

        assert(weak.use_count() == 1);

        /* It looks fine */
    }

    assert(weak.use_count() == 0);

    /* aw, on a second thought, it really doesn't */

    return 0;
}

I am building and running this using g++-9.3.0 with boost-1.72.0-1.

Clearly, the weak_ptr gets itself a shared_ptr "somewhere" as it can be used after being read. I was wondering "where it is stored and how to access it, if possible, without some `weak.lock()` call ?"

It looks like the shared_ptr gets destroyed at the same time as the archive, so I guess it is cleaned up thanks to the delete_created_pointers method when the iarchive is destroyed : does delete_created_pointers actually contains also shared_ptr ? As far as I can see in the code serialization/weak_ptr.hpp, we actually save then load a std::shared_ptr, so I was expecting the weak_ptr to expire as soon as the call returns : since it is not, there *is* another reference.

Now, I know I must save the shared_ptr in the archive myself to have my own reference and to get all weak_ptr running properly. But I came across this use case and I can't take it out of my mind, so if you have an explanation, I'm all ears.

Thank you