3

I need single ownership for an object because I need to be able to destroy it on demand (this makes sense sometimes; in this case the object represents a logged-in session that, for security reasons, the user wants to close). Let's call this object session. Other client objects keep references to session, but, of course, it may be dead when the clients access the reference.

What I'm after is a 'safe reference' that is notified when the original object is destroyed and reports this gracefully (exception, boolean) to the client, instead of a segfault.

Does anything like this exist? Preferable using what's available in standard C++/Boost. Preferably C++03. shared_ptr with weak_ptr is almost what I'm after, if only the shared_ptrs didn't extend the lifetime of session. I need to guarantee that session has been destroyed and a stray shared_ptr would prevent that.

thehouse
  • 7,957
  • 7
  • 33
  • 32
  • 1
    Based on the ownership criteria, it seems more like you want a [`std::unique_ptr`](http://en.cppreference.com/w/cpp/memory/unique_ptr). Unfortunately it's not possible to implement in C++03 so no Boost equivalent. – Some programmer dude Dec 04 '13 at 11:25
  • there is a design pattern for this very thing – Ariel Pinchover Dec 04 '13 at 11:25
  • http://stackoverflow.com/questions/17536731/shared-ptr-is-to-weak-ptr-as-unique-ptr-is-to-what –  Dec 04 '13 at 11:25
  • @JoachimPileborg That might work. The deleter could have a shared_ptr to a 'session_health' object that it sets to 'dead' on deletion. Shame I don't have `unique_ptr` in C++03. – thehouse Dec 04 '13 at 11:29
  • @Nik Really? How do I force destruction of the `session` object. – thehouse Dec 04 '13 at 11:30
  • @user634175 The answers to that question just suggest raw pointers, which aren't notfied of object destruction, or `weak_ptr` which depends on `shared_ptr` with its uncontrollable lifetime. – thehouse Dec 04 '13 at 11:32
  • @Infested There sure is. It's the notify-observer pattern. I'm trying to see if the tools to implement it already exist. – thehouse Dec 04 '13 at 11:34
  • no i mean there is a dp for counting how many objects point to the same place – Ariel Pinchover Dec 04 '13 at 11:35
  • Wrap your pointer in a class and set the wrapper pointer to "NULL" when you destroy the "pointed-to" thing. Then pass around a shared pointer to an instance of that class? – jcoder Dec 04 '13 at 11:37
  • @thehouse I had answered in haste, with less thought. Please check my new answer, and comment. – Nik Dec 04 '13 at 20:56

4 Answers4

2

There is a fundamental problem with the design you are requesting.

Suppose you have a session and a user of the session. The user checks that the session is valid, then uses it. Meanwhile the session becomes invalid, between checking and use. weak_ptr deals with this by allowing the user to upgrade to shared_ptr, then check and use that. Your design precludes that.

If you are willing to ignore that problem, I have a solution.

The object keeps a shared_ptr<void> (allocated as a char) as a member. It exposes weak_ptr<void> which can be used to track its lifetime, but not determine it. The object itself is unique_ptr stored, and the users keep a raw pointer and lifetime tracker pair.

This does not provide for immediate update of lifetime termination: for that use the callback pattern.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • You're right, but the situation you describe would only occur in multi-threaded code. We would already need to be locking in various places to prevent race conditions. This would just be another. – thehouse Dec 04 '13 at 11:52
  • Are you sugesting using the`char` pointed to by the `shared_ptr` as a boolean 'I'm alive' flag? – thehouse Dec 04 '13 at 11:54
  • 2
    @thehouse no. It is just the smallest thing you can create as a `shared_ptr`. The existence is the 'I am alive' flag: the object whose lifetime you want to track owns the `shared_ptr`, so their lifetimes coincide. And while multi threading is one way for the above problem to occur, it is not the only one: everytime you leave local flow control (call a function say) you have to prove it did not delete the session, which is a pain. – Yakk - Adam Nevraumont Dec 04 '13 at 12:44
1

Maybe something like:

class Session
{
    private:
    class Implementation {};

    public:
    Session()
    :   m_self(0) // You might do better, here
    {}

    ~Session() { delete m_self; }

    private:
    Session(const Session&); // No copy
    Session& operator = (const Session&); // No copy

    public:
    bool valid() const { return bool(m_self); }
    void terminate() { delete m_self; m_self = 0; }

    private:
    Implementation* m_self;
};

The class above has no similarity to std::shared_ptr or std::unique_ptr. Each session object can be passed by reference, only.

If you need to signal the termination of the session (or other events) I suggest putting boost::signal2 into the implementation.

  • This essentially turns `Session` into the smart reference and the original session becomes `Implementation`. I guess I'd hoped for something more generic and less intrusive. I think `shared_ptr>` would do the same thing, no? – thehouse Dec 04 '13 at 11:42
  • `optional_session->swap(optional())` would be equivalent to your `session.terminate()`, I think? And then all clients could do `if (optional_session)` to mean the same as `if (session.valid())`. – thehouse Dec 04 '13 at 11:48
0

Session class is wrapped inside SharedSession, which has a single reference to Session. SharedSession class provides behavior of Session class to clients. Client can close session and all other references when try to access the session get an exception. Locking in case of threads has not been covered in this code example.

#include<iostream>
#include<utility>

//- single object ownership
//- ensure shared object is not deleted by one of the reference deletion;
//- client should be notified when pointed memory is deleted explicitly on request.

//Proxy class for Session
class SharedSession{

  //Session class
  class Session{

    public:

      Session()
      {
        std::cout<<"Session ctr"<<std::endl;
      }

      bool isValidSession()
      {
        std::cout<<"Is valid session"<<std::endl;
      }

      bool login(std::string user,std::string password,std::string& sessionId)
      {
        std::cout<<"Login session"<<std::endl;
        //authenticate user - password and generate session id.
        sessionId = "abd123";
        return true;
        //return false //in case of failure
      }

      ~Session()
      {
        std::cout<<"Session dtr"<<std::endl;
      }


    private:

      std::string _userName;
      std::string _password;
      std::string _sessionId;

      Session(const Session& rhs){}
      Session& operator=(const Session& rhs){}
  };

  Session* _sessionInstance;
  //number of SharedSession objects created
  static int _sessionCount;
  //single ownership of sesion maintained in pair
  static std::pair<int,Session*> _sessionPair;
  //maintain state of session 
  static std::pair<bool,int> _sessionValid;

  public:

  SharedSession()
  {
    ++_sessionCount;
    std::cout<<"Shared Session "<<_sessionCount<<std::endl;
    if(_sessionCount == 1)
    {
      _sessionInstance = new Session();
      _sessionPair = std::make_pair(1,_sessionInstance);
      _sessionValid = std::make_pair(true,_sessionCount);
    }
    if(!_sessionValid.first)
      throw -1;//session is deleted
    else
    {
      _sessionValid.second = _sessionCount;
      _sessionInstance = NULL;
    }
  }

  ~SharedSession()
  { 
    std::cout<<"Shared session dtr  "<<_sessionValid.second<<std::endl;
    if(_sessionValid.second == 1 && _sessionValid.first)
    {
      delete (_sessionPair.second);
      std::cout<<"session deleted"<<std::endl;
      _sessionPair.second = NULL;
      _sessionValid.first = false;
    }
    _sessionValid.second -= 1;
    --_sessionCount;
  }

  bool closeSession()
  {
    //invalidate session
    _sessionValid.first = false;
    std::cout<<"close session"<<std::endl;
    delete _sessionPair.second;
  }

  bool isValidSession()
  {
    std::cout<<"session state "<<_sessionValid.first<<std::endl;
    if(_sessionValid.first)
      _sessionPair.second->isValidSession();
    else
      //notify client about deleted session
      throw -1;
  }

  bool login(std::string user,std::string password,std::string& sessionId)
  {
    if(_sessionValid.first)
      return _sessionPair.second->login(user,password,sessionId);
      //notify client about deleted session
    throw -1;
  }

  //any other operations which Session class exposes
};

int SharedSession::_sessionCount = 0;
std::pair<int,SharedSession::Session*> SharedSession::_sessionPair;
std::pair<bool,int> SharedSession::_sessionValid;

int main()
{
  SharedSession session1;
  SharedSession session2;
  try
  {
    session1.closeSession();
    session2.isValidSession();
  }
  catch(int& error)
  {
    std::cout<<"Exception occured  "<<error<<std::endl;
    std::cout<<"Session already deleted."<<std::endl;
  }
  return 0;
}
Nik
  • 1,294
  • 10
  • 16
-1

What about something like this :

#include <assert.h>

template <class T>
class notify_ptr
{
public:
    notify_ptr(T* ptr):m_ptr(ptr){};
    void kill()
    {
        if (m_ptr)
        {
            delete m_ptr;
            m_ptr = 0;
        }
    }
    bool isAlive()
    {
        return m_ptr!=0;
    }   
    T* operator->()
    {
        if (!m_ptr)
        {
            assert("Using a dead reference !");
            return 0; // segfault
        }
        return m_ptr;
    }
private:
    T* m_ptr;
};

class session
{
public:
    session():i(0){}
    void AddOne(){++i;}
private:
    int i;
};

void bar(notify_ptr<session>& mySession)
{
    mySession->AddOne();
    mySession.kill();
}

int main()
{
    notify_ptr<session> ptr(new session);
    bar(ptr);
    if (ptr.isAlive())
    {
        ptr->AddOne();
    }
    return 0;
}
eranb
  • 89
  • 8
  • This doesn't work because copies of the pointer don't share their aliveness. You kill the object via one copy of `notify_ptr` and the other copy tells you its still alive. – thehouse Dec 04 '13 at 11:59