序列化std :: shared_ptr<>进出二进制流

时间:2017-05-17 18:26:07

标签: c++ c++11 serialization stream shared-ptr

我有一个用C ++编写的自定义通用序列化系统,我处理了内在函数,std::string和包含它们的结构。但是,对于包含std::vector<byte>的内存流类,我希望能够在其中存储和检索std::shared_ptr<T>(其中T是从{派生的Abstract的任何类{1}})。当然,我想要一个解决方案而不使用Boost ,因为它会打败我的意图。

正如Addevent alertDialog所述:

  

使用另一个shared_ptr拥有的原始底层指针构造一个新的shared_ptr会导致未定义的行为。

我到目前为止唯一(hacky)解决方案是二进制内存流类具有由原始指针本身引用的std::shared_ptr<Abstract>的小查找表,使得读取和写出它们相当简单,所有权/参考计数是可靠的。然后序列化原始指针变得可行/有用。

但是,所有权/引用计数并不重要,因为它保证用例。如果有一个只使用std::vector<byte>的解决方案,我会认为这是一种更优雅的方法,因为它可以提供其他用例。

1 个答案:

答案 0 :(得分:2)

由于序列化/反序列化过程在同一进程(即相同的内存空间)中发生,因此您可以将原始内存指针存储为流中的二进制数据。考虑下面的想法,写作一个简单的演示。

不幸的是,std::enable_shared_from_this不允许手动递增/递减引用计数器,因为它只是存储一个弱引用,它无法在内部销毁ref == 0上的对象。这就是为什么我们必须进行手动引用管理,特别是对于字节流中的实例。

class Abstract : public std::enable_shared_from_this<Abstract> {
public:
  Abstract() : _count(0) {}

  ~Abstract() { cout << "I am destoryed" << endl; }

  void incrementStreamRef() {
    std::lock_guard<std::mutex> lock(_mutex);
    if (!_count) {
      _guard = this->shared_from_this();
    }
    ++_count;
  };

  void decrementStreamRef() {
    std::lock_guard<std::mutex> lock(_mutex);
    if (_count == 0)
      return;

    if (_count == 1) {
      if (_guard.use_count() == 1) {
        // After this call `this` will be destroyed
        _guard.reset();
        return;
      }
      _guard.reset();
    }
    --_count;
  };

private:
  std::mutex _mutex;
  std::shared_ptr<Abstract> _guard;
  std::size_t _count;
};

void addAbstractToStream(std::vector<uint8_t>& byteStream, Abstract* abstract) {
  abstract->incrementStreamRef();
  auto offset = byteStream.size();
  try {
    // 1 byte for type identification
    byteStream.resize(offset + sizeof(abstract) + 1);
    byteStream[offset]
      = 0xEE; // Means the next bytes are the raw pointer to an Abstract instance
    ++offset;
    // Add the raw pointer to the stream
    // prealocate memory here
    // byteStream.push_back(....;
    // ....
  } catch (...) {
    abstract->decrementStreamRef();
    return;
  }
  std::memcpy(byteStream.data() + static_cast<std::ptrdiff_t>(offset),
              (void*)&abstract,
              sizeof(abstract));
}

void removeAbstractFromStream(std::vector<uint8_t>& byteStream, std::size_t offset) {
  Abstract* abstract;

  std::memcpy((void*)&abstract,
              byteStream.data() + static_cast<std::ptrdiff_t>(offset),
              sizeof(abstract));

  abstract->decrementStreamRef();
}

void tryMe(std::vector<uint8_t>& byteStream) {
  // Must not be destoryed when we leave the scope
  auto abstract = std::make_shared<Abstract>();

  addAbstractToStream(byteStream, abstract.get());

  cout << "Scope is about to be left" << endl;
}

int main() {
  // Always walk over the stream and use `removeAbstractFromStream`
  std::vector<uint8_t> byteStream;

  // `try` to always clean the byte stream
  // Of course RAII is much better
  try {
    // Do some work with the stream
  } catch (...) {
    removeAbstractFromStream(byteStream, 1);
    throw;
  }

  tryMe(byteStream);

  cout << "Main is about to be left" << endl;
  removeAbstractFromStream(byteStream, 1);
  cout << "Main is even closer to be left" << endl;

  return 0;
}

当然,如果线程安全性不是问题,那么更精细的锁定可能没问题,或者完全丢弃。请在生产中使用之前修改角落案例的代码。