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

48

Ооп: Лекция 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, на родительский объект автоматически распространяется такой же запрет.

Эти кратко описанные механизмы взаимодействия родительского и дочернего (дочерних) объектов подробно рассматриваются ниже в большом количестве примеров.