Смарт-поинтеры является очень важным механизмом управления временем жизни объектов. В Qt присутствует модель управления временем жизни объектов, когда объекты наследуются от базового класса QObject и задается “родство” — parent/child. При удалении объекта, он удаляет всех своих child. Эта модель управления временем жизни объектов очень хорошо сочетается с технологией взаимодействия между объектами “Signals & Slots” и при использовании смарт-поинтеров могут возникнуть тяжело отлаживаемые баги.
В данной статье будет рассмотрен смарт-поинтер QScopedPointer, который является “облегченной” версией QSharedPointer. QScopedPointer введен в версии 4.6.
В boost присутствуют аналоги Qt-шным смарт-поинтерам:
QSharedPointer — boost::shared_ptr
QWeakPointer — boost::weak_ptr
QScopedPointer — boost::scoped_ptr
Простая программа, у которой не удаляется объект m_socket и соответственно, при завершении приложения, не происходит отключения сокета:
После запуска программы получаем такой вывод:
QAbstractSocket::ConnectingState
QAbstractSocket::ConnectedState
~test_scop_ptr_obj
Теперь изменим строку:
на строку:
и получим вывод:
QAbstractSocket::ConnectingState
QAbstractSocket::ConnectedState
~test_scop_ptr_obj
~test_socket
Добавим смарт-поинтер и теперь класс test_scop_ptr_obj будет выглядеть так:
Получаем вывод:
QAbstractSocket::ConnectingState
QAbstractSocket::ConnectedState
~test_scop_ptr_obj
~test_socket
QAbstractSocket::UnconnectedState
Пример 1 не интересен, так как в этом примере происходят утечки памяти.
Пример 2 и 3 отличаются технологией освобождения ранее выделенной памяти. И в том и другом примере происходит удаление объекта класса test_socket, но в 3-ем примере происходит вызов функции on_state_changed уже после вызова деструктора ~test_scop_ptr_obj, что является ошибкой. Перед удалением своих child, объект parent делает disconnect своим сигналам и слотам, поэтому в примере 2 и не приходит сигнал stateChanged к объекту test_scop_ptr_obj.
Для исправления ошибки в примере 3 необходимо в деструктор test_scop_ptr_obj дописать отключение сигналов удаляемого объекта:
В этом случае вывод будет таким-же как и в примере 2.
В данной статье будет рассмотрен смарт-поинтер QScopedPointer, который является “облегченной” версией QSharedPointer. QScopedPointer введен в версии 4.6.
В boost присутствуют аналоги Qt-шным смарт-поинтерам:
QSharedPointer — boost::shared_ptr
QWeakPointer — boost::weak_ptr
QScopedPointer — boost::scoped_ptr
Пример 1
Простая программа, у которой не удаляется объект m_socket и соответственно, при завершении приложения, не происходит отключения сокета:
class test_socket : public QTcpSocket
{
public:
test_socket( QObject * parent )
: QTcpSocket( parent )
{
}
virtual ~test_socket( )
{
qDebug( ) << "~test_socket";
}
};
class test_scop_ptr_obj : public QObject
{
Q_OBJECT
test_socket * m_socket;
public:
test_scop_ptr_obj( )
: m_socket( new test_socket( 0 ) )
{
m_socket->connectToHost( "www.ru", 80 );
connect( m_socket, SIGNAL( stateChanged ( QAbstractSocket::SocketState ) ), SLOT( on_state_changed( QAbstractSocket::SocketState ) ) );
}
virtual ~test_scop_ptr_obj( )
{
qDebug( ) << "~test_scop_ptr_obj";
}
private Q_SLOTS:
void on_state_changed( QAbstractSocket::SocketState socket_state )
{
qDebug( ) << socket_state;
}
};
int main( int argc, char** argv )
{
QCoreApplication a( argc, argv );
test_scop_ptr_obj obj;
QTimer::singleShot( 3000, &a, SLOT( quit( ) ) );
return a.exec( );
}
После запуска программы получаем такой вывод:
QAbstractSocket::ConnectingState
QAbstractSocket::ConnectedState
~test_scop_ptr_obj
Пример 2
Теперь изменим строку:
: m_socket( new test_socket( 0 ) )
на строку:
: m_socket( new test_socket( this ) )
и получим вывод:
QAbstractSocket::ConnectingState
QAbstractSocket::ConnectedState
~test_scop_ptr_obj
~test_socket
Пример 3
Добавим смарт-поинтер и теперь класс test_scop_ptr_obj будет выглядеть так:
class test_scop_ptr_obj : public QObject
{
Q_OBJECT
QScopedPointer< test_socket > m_socket;
public:
test_scop_ptr_obj( )
: m_socket( new test_socket( 0 ) )
{
m_socket->connectToHost( "www.ru", 80 );
connect( m_socket.data( ), SIGNAL( stateChanged ( QAbstractSocket::SocketState ) ), SLOT( on_state_changed( QAbstractSocket::SocketState ) ) );
}
virtual ~test_scop_ptr_obj( )
{
qDebug( ) << "~test_scop_ptr_obj";
}
private Q_SLOTS:
void on_state_changed( QAbstractSocket::SocketState socket_state )
{
qDebug( ) << socket_state;
}
};
Получаем вывод:
QAbstractSocket::ConnectingState
QAbstractSocket::ConnectedState
~test_scop_ptr_obj
~test_socket
QAbstractSocket::UnconnectedState
Анализ
Пример 1 не интересен, так как в этом примере происходят утечки памяти.
Пример 2 и 3 отличаются технологией освобождения ранее выделенной памяти. И в том и другом примере происходит удаление объекта класса test_socket, но в 3-ем примере происходит вызов функции on_state_changed уже после вызова деструктора ~test_scop_ptr_obj, что является ошибкой. Перед удалением своих child, объект parent делает disconnect своим сигналам и слотам, поэтому в примере 2 и не приходит сигнал stateChanged к объекту test_scop_ptr_obj.
Итоги
Для исправления ошибки в примере 3 необходимо в деструктор test_scop_ptr_obj дописать отключение сигналов удаляемого объекта:
virtual ~test_scop_ptr_obj( )
{
m_socket->disconnect( );
qDebug( ) << "~test_scop_ptr_obj";
}
В этом случае вывод будет таким-же как и в примере 2.