Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ООП_ Лекция №09 - Композиция объектов.docx
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
754.21 Кб
Скачать

Множественная композиция объектов-сущностей

Рассмотренный ранее пример содержал множественную композицию объектов-значений. Основная отличительная особенность любых объектов-значений - возможность свободного копирования и присвоение подобно встроенным типам. Наличие у объектов-точек такой возможности позволило легко поместить их в вектор.

Часто существует необходимость во множественной композиции объектов-сущностей, которые, как правило, не предполагают или даже полностью запрещают копирование, поскольку двух одинаковых объектов-сущностей в программе одновременно существовать не должно. Из невозможности копирования объектов вытекает невозможность помещения объектов в вектор. Также в вектор нельзя помещать ссылки на объекты, поскольку ссылка не является ни копируемой, ни перемещаемой в принципе. Очевидно, в таком случае остается лишь одно решение - вместо объектов или ссылок на объекты в вектор следует поместить указатели на объекты. Указатели могут копироваться и имеют значение по умолчанию не зависимо от типа данного (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;

}

//************************************************************************