- •Ооп: Лекция 9. Композиция объектов.
- •Композиция объектов
- •Простейшая композиция по значению
- •Множественная композиция по значению
- •Ссылочная композиция — логическое объединение
- •Композиция с разрываемой связью
- •Разрываемая ссылочная композиция
- •Недетерминированная множественность композиции
- •Множественная композиция объектов-сущностей
- •Полные примеры из лекции
Ооп: Лекция 9. Композиция объектов.
Версия 3.0 27 августа 2016г.
(С) 2013-2016, Зайченко Сергей Александрович, к.т.н, ХНУРЭ, доцент кафедры АПВТ
Композиция объектов
КОМПОЗИЦИЯ (или агрегирование, включение) - простейший способ создания новых более сложных классов путем объединения нескольких объектов существующих классов в единое целое.
Между классом верхнего и нижнего уровня обычно присутствует отношение "целое-часть". Ниже приведен простейший пример композиции — объект двигатель (Engine) является частью объекта автомобиль (Car):
engine.hpp
#ifndef _ENGINE_HPP_
#define _ENGINE_HPP_
//************************************************************************
// Класс для двигателя
class Engine
{
/*-----------------------------------------------------------------*/
public:
/*-----------------------------------------------------------------*/
// Конструктор
Engine ( float _horsePower )
: m_horsePower( _horsePower )
{}
// Метод доступа к мощности
float getHorsePower () const { return m_horsePower; }
/*-----------------------------------------------------------------*/
private:
/*-----------------------------------------------------------------*/
// Мощность в лошадиных силах
const float m_horsePower;
/*-----------------------------------------------------------------*/
};
#endif // _ENGINE_HPP_
car.hpp
#ifndef _CAR_HPP_
#define _CAR_HPP_
//************************************************************************
#include <string>
#include <iostream>
//************************************************************************
// Класс для автомобиля
class Car
{
/*-----------------------------------------------------------------*/
public:
/*-----------------------------------------------------------------*/
// Конструктор
Car ( const std::string & _model, float _engineHorsePower );
// Метод доступа к двигателю
const Engine & getEngine () const;
// Метод доступа к названию модели
const std::string & getModel () const
// Метод вывода описания машины в указанный поток
void describe ( std::ostream & _o ) const
/*-----------------------------------------------------------------*/
private:
/*-----------------------------------------------------------------*/
// Название модели
std::string m_model;
// Дочерний объект-двигатель
Engine m_engine;
/*-----------------------------------------------------------------*/
};
//************************************************************************
// Метод доступа к дочернему объекту-двигателю
inline const Engine &
Car::getEngine () const
{
return m_engine;
}
//************************************************************************
// Метод доступа к модели
inline const std::string &
Car::getModel () const
{
return m_model;
}
//************************************************************************
#endif // _CAR_HPP_
car.cpp
//************************************************************************
#include “car.hpp”
//************************************************************************
// Конструктор
Car::Car ( const std::string & _model, float _engineHorsePower )
: m_model( _model ),
m_engine( _engineHorsePower ) // <- вызов конструктора дочернего объекта
{}
//************************************************************************
// Тестовый метод, печатает информацию об автомобиле в поток
void Car::describe ( std::ostream & _o ) const
{
_o << "Model: " << getModel()
<< ", horse power: " << m_engine.getHorsePower()
<< std::endl; // ^- обращение к дочернему объекту
}
//************************************************************************
В памяти поля дочернего объекта размещаются внутри родительского объекта целиком:
К слову, объект std::string m_model также является дочерним объектом, и размещается полностью в родительском объекте Car, с той разницей, что модель - является объектом-значением, а двигатель - объектом-сущностью.
При обращении через указатель this родительского объекта добавляется смещение и получают указатель this на дочернем объекте. Никаких служебных данных времени выполнения для организации композиции в родительском объекте не требуется, все смещения дочерних объектов и других полей класса представляется возможным подсчитать автоматически во время компиляции.
В одном объекте может быть несколько дочерних объектов - одного или разных типов. Дочерние объекты могут выделяться как вместе с родительским, так и отдельно в динамической памяти. При этом родительский класс может быть ответственен либо не ответственен за уничтожение дочерних объектов в зависимости от ситуации. Дочерний объект может храниться по значению, если между ними имеется неразрывная зависимость от существования (по смыслу никогда нельзя отделить дочерний объект от родительского). Если зависимость менее жесткая и допускается замена или отцепление дочернего объекта, то обычно родительский объект хранит указатель на дочерний. Также может существовать слабая связь ссылочного типа, как правило, по константной ссылке, означающая логическое объединение понятий в некую группу, а не жесткую связь целое-часть.
Если дочерний объект хранится в родительском по значению, как двигатель в примере выше, и его конструктор имеет обязательные к указанию аргументы, то единственным местом их указания является список инициализации в конструкторе родительского класса:
// Конструктор
Car::Car ( const std::string & _model, float _engineHorsePower )
: m_model( _model ),
// ^ вызов конструктора копий дочернего объекта №1
m_engine( _engineHorsePower )
// ^ вызов обычного конструктора дочернего объекта №2
{
}
В свою очередь, дочерний объект-строка также инициализируется в списке инициализации, используя конструктор копий. Его присвоение возможно и в теле конструктора Car в отличие от двигателя, поскольку класс std::string имеет конструктор по умолчанию:
// Конструктор
Car::Car ( const std::string & _model, float _engineHorsePower )
: m_engine( _engineHorsePower )
// ^ вызов обычного конструктора дочернего объекта №2
{
m_model = _model;
}
Однако такой подход может быть не эффективен с точки зрения производительности, если дочерний объект выделит избыточные ресурсы в своем конструкторе по умолчанию, которые затем вскоре будут освобождены и заменены присвоением в теле конструктора.
Вызов деструкторов дочерних объектов, хранящихся в родительском объекте по значению, размещается автоматически в деструкторе родительского класса. Это справедливо как для случая автоматически генерируемого деструктора родительского класса, так и для случая, когда имеется собственный деструктор. Явно деструктор дочерних объектов вызывать не требуется.
Если же дочерний объект хранится по указателю, и при этом родительский объект отвечает за его уничтожение, то обязательно нужно в явном виде удалять дочерний объект в деструкторе родительского класса. В противном случае произойдет утечка памяти. Обычно, для таких родительских классов полностью запрещают копирование и присвоение, во избежания некорректного разделения дочернего объекта.
Если дочерний объект хранится по значению, и при этом его конструктор копий или операторы присвоения/перемещения закрыты в зоне влияния спецификатора доступа private, на родительский объект автоматически распространяется такой же запрет.
Эти кратко описанные механизмы взаимодействия родительского и дочернего (дочерних) объектов подробно рассматриваются ниже в большом количестве примеров.
