Wednesday, 6 November 2013

RAII + move semantics = ?

After last post about RAII I was looking for parts of Kadu code that could use this pattern.

One example was particularly interesting: before each write to libgadu socket notifiers must be disabled. And after each write they have to be enabled again. All writes are done by calling some libgadu functions with pointer to gg_session structure and, of course, some other parameters.

But there was nothing in Kadu implementation that prevented calling these functions without blocking socket notifiers. So I thought of extending our RAII class to be the only place where gg_session pointer could be acquired. I did it by making it movable (but not copyable). Result was named "token":

class GaduWritableSessionToken
{
  GaduConnection *Connection;

public:
  GaduWritableSessionToken(GaduConnection *connection) :
      Connection(connection)
  {
    Connection->beginWrite();
  }

  GaduWritableSessionToken(const GaduWritableSessionToken &copyMe)
    = delete;

  GaduWritableSessionToken(GaduWritableSessionToken &&moveMe)
  {
    Connection = moveMe.Connection;
    moveMe.Connection = nullptr;
  }

  ~GaduWritableSessionToken()
  {
    if (Connection)
      Connection->endWrite();
  }

  GaduWritableSessionToken & operator =
    (const GaduWritableSessionToken &copyMe) = delete;

  GaduWritableSessionToken & operator =
    (GaduWritableSessionToken &&moveMe)
  {
    Connection = moveMe.Connection;
    moveMe.Connection = nullptr;

    return *this;
  }

  gg_session * rawSession() const
  {
    return Connection ? Connection->rawSession() : nullptr;
  }

};

This looks pretty much like FileHandle class from last entry, but with two exceptions: it does not control resource lifetime but access pattern; and it has move semantics.

Next we need to replace gg_session getter from GaduConnection object with a method to acquire token:

GaduWritableSessionToken GaduConnection::writableSessionToken()
{
  return {this};
}

(This new C++ uniform initialization syntax looks pretty weird in this case).

Now it is the only way to obtain our protected resource. Thanks to move semantics we are sure that no instance of GaduWritableSessionToken will be ever duplicated in dangerous way (degenerated moved-from instances do not count) and only one pair of acquire/release methods (beginWrite()/endWrite()) will be called for each token. It can be moved (but not copied!) between scopes of objects and functions and can be used to safely access resource for its whole lifetime. When token goes out of its last scope, it is destroyed and its resource is released. Also, at the same moment, gg_session is no longer available.

This code is of course not thread-safe, but Kadu only uses separate threads to handle storing and retrieving history entries, so for our application it is not needed. For multithreaded code method for creating token should also disallow creating another one when first still exists. This can be easily implemented with some condition variables.

I hope that someone will find this useful.

Thanks for beevvy for pointing out that making this class movable is way better idea than to wrap it in std::unique_ptr.

No comments:

Post a Comment