Безопасная ссылка в C++ (указатель единого владельца с семантикой уведомления)
Мне нужно единоличное владение объектом, потому что я должен иметь возможность уничтожить его по требованию (иногда это имеет смысл; в этом случае объект представляет собой зарегистрированный сеанс, который по соображениям безопасности пользователь хочет закрыть). Давайте назовем этот объект session
, Другие объекты клиента хранят ссылки на session
, но, конечно, он может быть мертвым, когда клиенты обращаются к ссылке.
То, что мне нужно, - это "безопасная ссылка", которая уведомляется, когда исходный объект уничтожается, и сообщает об этом изящно (исключение, логическое значение) клиенту, а не segfault.
Существует ли что-нибудь подобное? Предпочтительно использовать то, что доступно в стандартном C++/Boost. Предпочтительно C++03. shared_ptr
с weak_ptr
почти то, что я после, если только shared_ptr
с не продлил жизнь session
, Я должен гарантировать, что session
был разрушен и заблудился shared_ptr
помешает этому.
4 ответа
Существует фундаментальная проблема с дизайном, который вы запрашиваете.
Предположим, у вас есть сеанс и пользователь сеанса. Пользователь проверяет, что сеанс действителен, а затем использует его. Между тем сеанс становится недействительным, между проверкой и использованием. weak_ptr
имеет дело с этим, позволяя пользователю обновить до shared_ptr
, затем проверьте и используйте это. Ваш дизайн исключает это.
Если вы готовы игнорировать эту проблему, у меня есть решение.
Объект хранит shared_ptr<void>
(выделяется как char
) в качестве члена. Это выставляет weak_ptr<void>
который может использоваться для отслеживания его времени жизни, но не для его определения. Сам объект unique_ptr
сохраняются, а пользователи сохраняют необработанный указатель и пару трекеров времени жизни.
Это не обеспечивает немедленного обновления завершения жизни: для этого используйте шаблон обратного вызова.
Может быть что-то вроде:
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 в реализацию.
Класс Session находится внутри 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;
}
Как насчет чего-то вроде этого:
#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;
}