Boost logo

Boost Users :

From: ypotin (yannp63_at_[hidden])
Date: 2020-05-06 00:37:57


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



Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net