Безопасная ссылка в C ++ (указатель единственного владения с семантикой уведомления)

Мне нужно единоличное владение объектом, потому что мне нужно иметь возможность уничтожить его по требованию (иногда это имеет смысл; в этом случае объект представляет собой сеанс входа в систему, который из соображений безопасности пользователь хочет закрыть). Назовем этот объект session. Другие клиентские объекты сохраняют ссылки на session, но, конечно, он может быть мертв, когда клиенты обращаются к ссылке.

Мне нужна «безопасная ссылка», которая уведомляется, когда исходный объект уничтожается, и аккуратно сообщает об этом (исключение, логическое значение) клиенту вместо segfault.

Есть ли что-нибудь подобное? Предпочтительно использовать то, что доступно в стандартном C ++ / Boost. Желательно C ++ 03. shared_ptr с weak_ptr - это почти то, что мне нужно, если бы только shared_ptrs не продлили срок службы session. Мне нужно гарантировать, что session был уничтожен, и случайный shared_ptr предотвратит это.


person thehouse    schedule 04.12.2013    source источник
comment
Судя по критериям владения, вам больше похоже, что вам нужен std::unique_ptr. К сожалению, это невозможно реализовать в C ++ 03, поэтому нет эквивалента Boost.   -  person Some programmer dude    schedule 04.12.2013
comment
есть шаблон дизайна для этой самой вещи   -  person Infested    schedule 04.12.2013
comment
stackoverflow.com/questions/17536731/   -  person    schedule 04.12.2013
comment
@JoachimPileborg Это может сработать. У удалителя может быть shared_ptr для объекта 'session_health', который он устанавливает как 'мертвый' при удалении. Жаль, что у меня нет unique_ptr в C ++ 03.   -  person thehouse    schedule 04.12.2013
comment
@Nik Серьезно? Как принудительно уничтожить объект session.   -  person thehouse    schedule 04.12.2013
comment
@ user634175 Ответы на этот вопрос просто предлагают необработанные указатели, которые не зависят от разрушения объекта, или weak_ptr, который зависит от shared_ptr с его неконтролируемым временем жизни.   -  person thehouse    schedule 04.12.2013
comment
@Infested. Конечно, есть. Это шаблон уведомляющего наблюдателя. Я пытаюсь понять, существуют ли уже инструменты для его реализации.   -  person thehouse    schedule 04.12.2013
comment
нет, я имею в виду, что есть dp для подсчета количества объектов, указывающих на одно и то же место   -  person Infested    schedule 04.12.2013
comment
Оберните указатель в класс и установите указатель оболочки на NULL, когда вы уничтожите объект, на который указывает указатель. Затем передать общий указатель на экземпляр этого класса?   -  person jcoder    schedule 04.12.2013
comment
@thehouse Я ответил поспешно, не задумываясь. Пожалуйста, проверьте мой новый ответ и прокомментируйте.   -  person Nik    schedule 05.12.2013


Ответы (4)


Существует фундаментальная проблема с дизайном, который вы запрашиваете.

Предположим, у вас есть сеанс и пользователь сеанса. Пользователь проверяет действительность сеанса, а затем использует его. Между тем между проверкой и использованием сеанс становится недействительным. weak_ptr решает эту проблему, позволяя пользователю перейти на shared_ptr, а затем проверить и использовать это. Ваш дизайн этого не допускает.

Если вы готовы игнорировать эту проблему, у меня есть решение.

Объект сохраняет shared_ptr<void> (выделенный как char) в качестве члена. Он предоставляет weak_ptr<void>, который можно использовать для отслеживания его срока службы, но не для его определения. Сам объект unique_ptr хранится, а пользователи хранят пару необработанных указателей и счетчиков времени жизни.

Это не обеспечивает немедленного обновления окончания срока службы: для этого используйте шаблон обратного вызова.

person Yakk - Adam Nevraumont    schedule 04.12.2013
comment
Вы правы, но описанная вами ситуация может возникнуть только в многопоточном коде. Нам уже нужно было бы блокироваться в разных местах, чтобы предотвратить состояние гонки. Это было бы совсем другое. - person thehouse; 04.12.2013
comment
Вы предлагаете использовать thechar, на который указывает shared_ptr<void>, как логический флаг "Я жив"? - person thehouse; 04.12.2013
comment
@ номер дома. Это самая маленькая вещь, которую вы можете создать как shared_ptr. Существование - это флаг «Я жив»: объект, время жизни которого вы хотите отслеживать, владеет shared_ptr, поэтому их время жизни совпадает. И хотя многопоточность является одним из способов возникновения вышеуказанной проблемы, она не единственная: каждый раз, когда вы покидаете локальное управление потоком (например, вызываете функцию), вы должны доказать, что он не удалил сеанс, что является проблемой. - person Yakk - Adam Nevraumont; 04.12.2013

Может быть что-то вроде:

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;
};

Приведенный выше класс не похож на std :: shared_ptr или std :: unique_ptr. Каждый объект сеанса может быть передан только по ссылке.

Если вам нужно сигнализировать о завершении сеанса (или других событий), я предлагаю добавить в реализацию boost :: signal2.

person Community    schedule 04.12.2013
comment
По сути, это превращает Session в интеллектуальную ссылку, а исходный сеанс становится Implementation. Думаю, я надеялся на что-то более общее и менее навязчивое. Я думаю, shared_ptr<optional<Implementation>> поступил бы так же, не так ли? - person thehouse; 04.12.2013
comment
Думаю, optional_session->swap(optional<Implementation>()) будет эквивалентно вашему session.terminate()? И тогда все клиенты могли делать if (optional_session), чтобы означать то же, что и if (session.valid()). - person thehouse; 04.12.2013

Класс сеанса заключен в SharedSession, который имеет единственную ссылку на Session. Класс SharedSession предоставляет клиентам поведение класса Session. Клиент может закрыть сеанс и все другие ссылки при попытке доступа к сеансу получить исключение. Блокировка в случае потоков не рассматривалась в этом примере кода.

#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;
}
person Nik    schedule 04.12.2013

Как насчет чего-то вроде этого:

#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;
}
person eranb    schedule 04.12.2013
comment
Это не работает, потому что копии указателя не разделяют их живучесть. Вы убиваете объект через одну копию notify_ptr, а другая копия сообщает вам, что он все еще жив. - person thehouse; 04.12.2013