- •Ооп: Лекция 9. Композиция объектов.
- •Композиция объектов
- •Простейшая композиция по значению
- •Множественная композиция по значению
- •Ссылочная композиция — логическое объединение
- •Композиция с разрываемой связью
- •Разрываемая ссылочная композиция
- •Недетерминированная множественность композиции
- •Множественная композиция объектов-сущностей
- •Полные примеры из лекции
Множественная композиция объектов-сущностей
Рассмотренный ранее пример содержал множественную композицию объектов-значений. Основная отличительная особенность любых объектов-значений - возможность свободного копирования и присвоение подобно встроенным типам. Наличие у объектов-точек такой возможности позволило легко поместить их в вектор.
Часто существует необходимость во множественной композиции объектов-сущностей, которые, как правило, не предполагают или даже полностью запрещают копирование, поскольку двух одинаковых объектов-сущностей в программе одновременно существовать не должно. Из невозможности копирования объектов вытекает невозможность помещения объектов в вектор. Также в вектор нельзя помещать ссылки на объекты, поскольку ссылка не является ни копируемой, ни перемещаемой в принципе. Очевидно, в таком случае остается лишь одно решение - вместо объектов или ссылок на объекты в вектор следует поместить указатели на объекты. Указатели могут копироваться и имеют значение по умолчанию не зависимо от типа данного (nullptr).
Например, вертолетная площадка (HelicopterPad) могла бы вести учет объектов-вертолетов (Helicopter), которые на ней приземлялись в естественном хронологическом порядке:
helicopterpad.hpp
#ifndef _HELICOPTERPAD_HPP_
#define _HELICOPTERPAD_HPP_
//************************************************************************
#include "point3d.hpp"
#include <vector>
//************************************************************************
// Форвардное объявление класса-вертолета.
// Его содержимое не требуется для объявления класса-площадки.
class Helicopter;
//************************************************************************
// Класс-площадка
class HelicopterPad
{
/*-----------------------------------------------------------------*/
public:
/*-----------------------------------------------------------------*/
...
// Метод регистрации приземления вертолета
void land ( Helicopter & _helicopter );
// Метод возвращает количество зарегистрированных приземлений
int getRegisteredLandingsCount () const;
// Метод возвращает ссылку на объект-вертолет среди прежних приземлений
Helicopter & getRegisteredLanded ( int _index ) const;
...
/*-----------------------------------------------------------------*/
private:
/*-----------------------------------------------------------------*/
// Последовательность вертолетов, приземлявшихся на данной площадке
std::vector< Helicopter * > m_landingHistory;
/*-----------------------------------------------------------------*/
};
//************************************************************************
...
//************************************************************************
#endif // _HELICOPTERPAD_HPP_
helicopterpad.сpp
...
// Реализация метода регистрации приземления вертолета
void HelicopterPad::land ( Helicopter & _helicopter )
{
// Убеждаемся, что другой вертолет не приземлялся
if ( m_pLanded )
throw std::logic_error( "Something has already landed on this pad" );
// Подводим вертолет к точке приземления
_helicopter.moveTo( getLocation() );
// Создаем связь между объектами — // запоминаем адрес объекта-вертолета в объекте-площадке
m_pLanded = & _helicopter;
// Регистрируем приземление в журнал
m_landingHistory.push_back( m_pLanded );
}
//************************************************************************
// Метод возвращает количество зарегистрированных приземлений
int HelicopterPad::getRegisteredLandingsCount () const
{
return m_landingHistory.size(); }
//************************************************************************
// Метод возвращает ссылку на объект-вертолет среди прежних приземлений
Helicopter & HelicopterPad::getRegisteredLanded ( int _index ) const
{
return * m_landingHistory.at( _index ); }
//************************************************************************
Отдельного внимания требуют случаи, когда родительский объект является ответственным за уничтожение дочерних объектов-сущностей. Помещение указателя на некоторый объект в вектор не является причиной для его автоматического уничтожения (вектор не может догадаться о намерении программиста), и в таких случаях следует позаботиться об уничтожении вручную.
Ниже представлен класс, моделирующий эскадрилью вертолетов (HelicopterEscadrille). Конкретные вертолеты могут входить в состав эскадрильи (join), выходить из нее (leave), либо уничтожаться (onUnitDestroyed), например, в результате боевых действий. Для хранения связей между эскадрильей и индивидуальными вертолетами, используем контейнер std::vector с указателями на объекты Helicopter. Отличие этого примера от истории приземлений вертолетов на площадке состоит в ответственности за уничтожение объектов-вертолетов при уничтожении объекта-эскадрильи:
helicopterescadrille.hpp
#ifndef _HELICOPTER_ESCADRILLE_HPP_
#define _HELICOPTER_ESCADRILLE_HPP_
//************************************************************************
#include <vector>
//************************************************************************
// Форвардное объявление класса-вертолета
class Helicopter;
//************************************************************************
// Класс-эскадрилья
class HelicopterEscadrille
{
/*-----------------------------------------------------------------*/
public:
/*-----------------------------------------------------------------*/
// Конструктор по умолчанию, необходим, т.к. имеется удаленный конструктор копий
HelicopterEscadrille () = default;
// Удаленные конструктор копий и оператор присвоения
HelicopterEscadrille ( const HelicopterEscadrille & ) = delete;
HelicopterEscadrille & operator = ( const HelicopterEscadrille & )= delete;
// Деструктор
~HelicopterEscadrille ();
// Метод, вычисляющий фактическое количество вертолетов
int getJoinedUnitsCount () const;
// Метод доступа к вертолету по порядковому номеру
Helicopter & getHelicopter ( int _index ) const;
// Метод поиска порядкового номера конкретного вертолета
int findHelicopter ( const Helicopter & _helicopter ) const;
// Метод присоединения вертолета к эскадрилье
void join ( Helicopter * _pHelicopter );
// Метод выхода вертолета из состава эскадрильи
void leave ( Helicopter & _helicopter );
// Метод регистрации факта уничтожения вертолета
void onUnitDestroyed ( Helicopter * _pHelicopter );
/*-----------------------------------------------------------------*/
private:
/*-----------------------------------------------------------------*/
// Вектор указателей на объекты-вертолеты
std::vector< Helicopter * > m_helicopters;
/*-----------------------------------------------------------------*/
};
//************************************************************************
// Метод, вычисляющий фактическое количество вертолетов
inline int
HelicopterEscadrille::getJoinedUnitsCount () const
{
return m_helicopters.size();
}
//************************************************************************
#endif // _HELICOPTER_ESCADRILLE_HPP_
helicopterescadrille.cpp
#include "helicopterescadrille.hpp"
#include "helicopter.hpp"
//************************************************************************
// Реализация деструктора
HelicopterEscadrille::~HelicopterEscadrille ()
{
// Уничтожаем каждый из вертолетов
for ( Helicopter * pHelicopter : m_helicopters )
delete pHelicopter;
}
//************************************************************************
// Реализация метода доступа к вертолету по порядковому номеру
Helicopter &
HelicopterEscadrille::getHelicopter ( int _index ) const
{
return * m_helicopters.at( _index );
}
//************************************************************************
// Реализация метода поиска порядкового номера конкретного вертолета
int
HelicopterEscadrille::findHelicopter ( const Helicopter & _helicopter ) const
{
// Проходим по массиву указателей и ищем вертолет с таким же адресом
int nHelicopters = m_helicopters.size();
for ( int i = 0; i < nHelicopters; i++ )
if ( m_helicopters[ i ] == ( & _helicopter ) )
return i; // <- позиция обнаружена
return -1; // вертолет не обнаружен
}
//************************************************************************
// Реализация метода присоединения вертолета к эскадрилье
void HelicopterEscadrille::join ( Helicopter * _pHelicopter )
{
// Убеждаемся, что данный вертолет уже не входит в состав эскадрильи
if ( findHelicopter( * _pHelicopter ) != -1 )
throw std::logic_error( "Helicopter has already joined the escadrille" );
// Помещаем адрес вертолета в конец вектора
m_helicopters.push_back( _pHelicopter );
}
//************************************************************************
// Метод выхода вертолета из состава эскадрильи
void HelicopterEscadrille::leave ( Helicopter & _helicopter )
{
// Убеждаемся в том, что вертолет входит в состав эскадрильи
int position = findHelicopter( _helicopter );
if ( position == -1 )
throw std::logic_error( "Helicopter is not a part of this escadrille" );
// Удаляем найденную позицию из вектора
m_helicopters.erase( m_helicopters.begin() + position );
}
//************************************************************************
// Метод регистрации факта уничтожения вертолета
void HelicopterEscadrille::onUnitDestroyed ( Helicopter * _pHelicopter )
{
// Выводим вертолет из состава эскадрильи
leave( * _pHelicopter );
// Уничтожаем вертолет
delete _pHelicopter;
}
//************************************************************************
Для реализации эскадрильи нужен явный деструктор, уничтожающий сами дочерние объекты до момента автоматического освобождения внутренних данных вектора:
HelicopterEscadrille::~HelicopterEscadrille ()
{
// Уничтожаем каждый из вертолетов
for ( Helicopter * pHelicopter : m_helicopters )
delete pHelicopter;
// НЕЯВНО:
// std::vector< Helicopter * >::~vector ( & m_helicopters )
}
Если этого действия не сделать, произодет утечка динамической памяти.
Ниже приведена тестовая программа, использующая вертолеты и эскадрильи:
test_escadrille.cpp
#include "helicopterescadrille.hpp"
#include "helicopter.hpp"
#include <cassert>
int main ()
{
// Создаем эскадрилью
HelicopterEscadrille * pEscadrille = new HelicopterEscadrille();
// Создаем 3 вертолета
Helicopter * pHelicopter1 = new Helicopter( 1 );
Helicopter * pHelicopter2 = new Helicopter( 2 );
Helicopter * pHelicopter3 = new Helicopter( 3 );
// Вводим все 3 вертолета в состав эскадрильи
pEscadrille->join( pHelicopter1 );
pEscadrille->join( pHelicopter2 );
pEscadrille->join( pHelicopter3 );
// Убеждаемся, что эскадрилья содержит 3 вертолета
assert( pEscadrille->getJoinedUnitsCount() == 3 );
// Выводим второй вертолет из состава эскадрильи.
// При этом, эскадрилья снимает с себя ответственность за его уничтожение,
// и это будет необходимо осуществить за пределами эскадрильи
pEscadrille->leave( * pHelicopter2 );
// В эскадрилье должно остаться 2 вертолета
assert( pEscadrille->getJoinedUnitsCount() == 2 );
// В результате боевых действий первый вертолет уничтожен.
// Регистрируем данный факт в эскадрилье, что уничтожит объект-вертолет.
pEscadrille->onUnitDestroyed( pHelicopter1 );
// В эскадрилье должен был остаться только один вертолет.
assert( pEscadrille->getJoinedUnitsCount() == 1 );
// Восполняем потери. Создаем еще один вертолет и присоединяем его к эскадрилье
Helicopter * pHelicopter4 = new Helicopter( 4 );
pEscadrille->join( pHelicopter4 );
// Теперь в ней 2 вертолета
assert( pEscadrille->getJoinedUnitsCount() == 2 );
// Уничтожаем эскадрилью и отдельно вертолет, не входящий в ее состав
delete pEscadrille;
delete pHelicopter2;
}
//************************************************************************
