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

Приемы применения статических членов — разделяемый ресурс

В приведенном следующем примере показано, как статическая переменная-член ms_DumpFile применяется для хранения разделяемого всеми объектами ресурса - файлового потока, открываемого в режиме записи. Данный файл используется для трассировки создания объектов в отладочных целях. Файл создается по требованию при попытке первого использования.

Деструктор файлового потока вызывается автоматически при выходе из функции main(), что освободит ресурсы операционной системы, связанные с открытым программой файлом.

student.hpp

#ifndef _STUDENT_HPP_

#define _STUDENT_HPP_

#include <iostream>

class Student

{

// Обычные переменные-члены

const char * m_LastName;

int m_Mark;

// Статическая переменная-член. Поток трассировочного файла

static std::fstream ms_DumpFile;

public:

// Конструктор

Student ( const char * _lastName, int _mark );

// Функция генерации отладочной трассировки для объекта

void Dump ();

};

#endif // _STUDENT_HPP_

student.cpp

#include “student.hpp”

// Определение статической переменной-члена, конструктор по умолчанию

std::fstream Student::ms_DumpFile;

// Реализация конструктора

Student::Student ( const char * _lastName, int _mark )

: m_LastName( _lastName ), m_Mark( _mark )

{

}

// Реализация функции генерации отладочной трассировки

void Student::Dump ()

{

// Если общий трассировочный файл еще не открыт, пора его открывать

if ( ! ms_DumpFile.is_open() )

{

// Открываем файл в режиме записи

ms_DumpFile.open( “students.txt”, std::ios_base::out );

}

// Отладочная трасса объекта

ms_DumpFile << "Student " << m_LastName << " - " << m_Mark << std::endl;

}

test.cpp

#include “student.hpp”

int main ()

{

Student s1( "Ivanov", 75 );

Student s2( "Petrov", 80 );

Student s3( "Sidorov", 60 );

s1.Dump();

s2.Dump();

s3.Dump();

}

В трассировочном файле после выполнения программы будет находиться следующее содержимое:

Student Ivanov – 75

Student Petrov – 80

Student Sidorov – 60

Приемы применения статических членов — Factory Method

Довольно распространенной ситуацией является случай, когда некоторые комбинации аргументов класса не являются допустимыми. Например, описанный выше класс Student не должен считать корректными значения оценки за пределами интервала [1;100]. Объект, нарушающие данное инвариантное условие существовать не должен, поскольку не соблюдается важное ограничение из рассматриваемой предметной области.

Обычные функции, сталкиваясь с подобной проблемой, осуществляют проверку, и предпринимают те или иные действия по обработке возникшей ошибки. Простейшим вариантом обработки является возврат значения, означающего неудачу (например, функция fopen возвращает nullptr, если файл открыть не удается) или генерация исключительной ситуации. Когда ошибка возникает в конструкторе, то возникают определенные сложности:

  • конструкторы не имеют возвращаемого типа, соответственно, не доступен вариант обработки с возвратом значения, означающего неудачу - доступно лишь решение, генерирующее исключительную ситуацию (инструкция throw);

  • конструктор должен после завершения работы оставить объект в корректном начальном состоянии, что, в случае передачи ошибочных аргументов может быть затруднительным и даже невозможным (либо генерировать исключительную ситуацию, оставив объект недоконструированным);

  • возможно, конструктор уже выделил часть ресурсов до момента обнаружения ошибки, и их требуется корректно освободить, поскольку деструкторы недоконструированных объектов не вызываются.

По тем или иным соображениям генерация исключительной ситуации для обработки нарушения инвариантных условий может быть нежелательной. Одной из возможных причин может быть относительно высокая стоимость генерации и перехвата исключений с точки зрения производительности. Кроме того, исключение в конструкторе объектов, выделяемых динамически, по-прежнему означает, что, помимо конструктора, вызывается пара глобальных операторов new/delete для низкоуровневого распределения памяти (new вызывается до вызова конструктора, а delete будет вызван автоматически при генерации исключения в конструкторе).

Решить описанную проблему можно применением статического МЕТОДА-ФАБРИКИ (Factory Method). Такой метод будет проверять корректность переданных аргументов до момента создания объекта, а лишь убедившись в их правильности, вызовет оператор mew и конструктор. Метод-фабрику обычно называют Create или Make. Сам конструктор может опустить все проверки и предполагать, что данные придут корректными. Чтобы исключить случайный прямой вызов конструктора, не делающего проверок аргументов, его следует поместить в зону доступа private. В таком случае создание объекта будет возможно только через вызов метода-фабрики.

Ниже приведены необходимые изменения для добавления метода-фабрики в классе Student:

student.hpp

#ifndef _STUDENT_HPP_

#define _STUDENT_HPP_

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

class Student

{

/*-----------------------------------------------------------------*/

// Обычные (нестатические) переменные-члены (храним в объекте)

const char * m_LastName;

const int m_Mark;

/*-----------------------------------------------------------------*/

// Закрытый конструктор

Student ( const char * _lastName, int _mark );

/*-----------------------------------------------------------------*/

public:

// Статический метод-фабрика

static Student* Make ( const char * _lastName, int _mark );

/*-----------------------------------------------------------------*/

};

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

#endif // _STUDENT_HPP_

student.cpp

#include "student.hpp"

#include <stdexcept>

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

// Реализация конструктора

Student::Student ( const char * _lastName, int _mark )

: m_LastName( _lastName ), m_Mark( _mark )

{

}

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

// Реализация статического метода-фабрики

Student * Student::Make ( const char * _lastName, int _mark )

{

// Делаем проверку ограничения на оценки

if ( _mark >= 1 && _mark <= 100 )

// Все корректно, можно создавать объект

return new Student( _lastName, _mark );

else

{

// Сигнализируем об ошибке, не создаем объект

throw std::logic_error( "Invalid student data" );

}

}

test.cpp

#include "student.hpp"

#include <cassert>

#include <stdexcept>

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

int main ()

{

// Создаем студента с корректным баллом через метод-фабрику.

// Ожидаем успех

Student * pStudent = Student::Make( "Ivanov", 75 );

delete pStudent;

// Создаем студента с превышающим допустимый баллом через метод-фабрику.

// Ожидаем неудачу

try

{

pStudent = Student::Make( "Petrov", 101 );

assert( ! "Exception must have been thrown!" );

}

catch ( std::exception & )

{

// Исключение ожидаемо

}

// Создаем студента со слишком маленьким баллом через метод-фабрику.

// Ожидаем неудачу

try

{

pStudent = Student::Make( "Sidorov", 0 );

assert( !"Exception must have been thrown!" );

}

catch ( std::exception & )

{

// Исключение ожидаемо

}

}

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