itmo.ru aco.ifmo.ru c-visionlab.ru
вернуться в оглавление предыдущая глава предыдущий параграф следующий параграф следующая глава


4.3 Конструкторы и деструкторы класса

Любой переменной, участвующей в работе программы, требуется память и некоторое начальное значение. Для переменных встроенных типов размещение в памяти обеспечивается компилятором. Для локальных переменных память выделяется из стека программы и занимается для хранения значения данной переменной до тех, пока не закончится время ее жизни. Сложные типы данных также должны размещаться в памяти и уничтожаться когда их время жизни закончилось. Это осуществляется с использованием конструкторов и деструкторов.

Конструктор (constructor) - это функция-член, имя которой совпадает с именем класса, инициализирующая переменные-члены, распределяющая память для их хранения (new).

// конструктор по умолчанию 
Lens(); 
// полный конструктор 
Lens(double r1, double r2, double D, double d, double n); 
// конструктор копирования 
Lens(const Lens& one); 

Деструктор (destructor) - это функция-член, имя которой представляет собой ~имя класса, предназначенная для уничтожения переменных (delete).

 ~Lens(); // деструктор 

Одной из особенностей конструктора и деструктора является то, что в отличие от всех остальных функций, у них нет возвращаемого значения.

4.3.1. Конструкторы

Конструктор по умолчанию

Конструктор, не требующий аргументов, называется конструктором по умолчанию. Конструктор по умолчанию не имеет аргументов и инициализирует все переменные члены какими-либо начальными значениями.

// описание конструктора по умолчанию 
Lens(); 

// реализация конструктора по умолчанию 
Lens::Lens() 
{ 
    m_R1=0.; 
    m_R2=0.; 
    m_d=0.; 
    m_D=0.; 
    m_n=1.; 
} 

При создании любого экземпляра класса вызывается конструктор. Если при описании экземпляра не указываются никакие параметры – вызывается конструктор по умолчанию:

// вызов конструктора по умолчанию 
Lens test_lens1; 
Lens test_lens2(); 
Lens test_lens3[10][10]; 

Полный конструктор

Полный конструктор позволяет явно инициализировать все переменные-члены класса.

// описание полного конструктора 
Lens(double r1,double r2,double D, double d, double n); 
// реализация полного конструктора 
Lens::Lens(double r1, double r2, double D, double d, double n) 
{ 
    m_R1=r1; 
    m_R2=r2; 
    m_d=d; 
    m_D=D; 
    m_n=n; 
} 

Если при описании экземпляра класса в скобках указать параметры, при создании экземпляра класса будет вызван полный конструктор и переменные-члены инициализируются указанными значениями.

// вызов полного конструктора 
Lens test_lens(10., -10., 2., 5., 1.5); 

Неполный конструктор

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

// описание неполного конструктора 
Lens(double r1, double r2); 

// реализация неполного конструктора 
Lens::Lens(double r1, double r2) 
{ 
   m_R1=r1; 
   m_R2=r2; 
   m_d=0.; 
   m_D=0.; 
   m_n=1.; 
} 

// вызов неполного конструктора 
Lens lens(10., -10.); 

Инициализация переменных-членов класса в конструкторах может осуществляться не только в теле конструктора, но и после оператора :. При этом, во время присваивания переменной-члену значения, будет вызываться не оператор присваивания, а конструктор. Для встроенных типов данных, таких как double или int, это не существенно, но если членами класса являются абстрактные типы, вызов конструктора вместо оператора присваивания будет выполняться быстрее.

Lens::Lens(double r1, double r2) 
: m_R1(R1) 
, m_R2(R2) 
{ 
    m_d=2.; 
    m_D=5.; 
    m_n=1.5; 
} 

или такой вариант:

Lens::Lens(double R1, double R2) 
: m_R1(R1) 
, m_R2(R2) 
, m_d(2.) 
, m_D(5.) 
, m_n(1.5) 
{ 
} 

Конструктор копирования

Конструктор копирования создает копию уже существующего экземпляра класса, копируя поэлементно переменные-члены. Конструктор копирования также используется при передаче экземпляров класса в функции по значению. Обратите внимание, что экземпляр класса передается в конструктор по константной ссылке.

// описание конструктора копирования 
Lens(const Lens& l); 

// реализация конструктора копирования 
Lens::Lens(const Lens& l) 
: m_R1(l.m_R1) 
, m_R2(l.m_R2) 
, m_d(l.m_d) 
, m_D(l.m_D) 
, m_n(l.m_n) 
{ 
}

// вызов конструктора копирования 
Lens lens1(10., -10.); 
Lens lens2(lens1); 

4.3.2. Деструктор (пример 4.4. Конструктор и деструктор класса Матрица)

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

В классе Lens никакого динамического размещения не происходило, поэтому деструктор будет пустой, но его наличие все равно обязательно. Для примера реализации деструктора, представим, что имеется класс Matrix, который в конструкторе динамически создает двумерный массив размерности n x m. Тогда деструктор должен освобождать память, которую выделяет конструктор.

Конструктор вызывается в момент создания переменной, деструктор вызывается когда время жизни переменной закончилось, то есть когда встречается закрывающая фигурная скобка } блока, в которой была объявлен экземпляр класса, либо когда вызывается оператор delete при динамическом размещении экземпляра класса.

/////////////////////////////////////////////////////////////////////////////
// Программирование на языке высокого уровня. Основы языка С++
// Пример 4.4. Класс Матрица
// matrix.h
// 
// http://aco.ifmo.ru/el_books/programming
// Университет ИТМО
/////////////////////////////////////////////////////////////////////////////
// проверка на повторное подключение файла
#if !defined MATRIX_H
#define MATRIX_H 

/////////////////////////////////////////////////////////////////////////////
// класс Матрица
class Matrix
{
private:
    //число строк и число столбцов
    int m_rows, m_cols;
    //указатель на динамический массив данных
    double* m_data;

public:
    //конструктор по умолчанию
    Matrix();
    //полный конструктор
    Matrix(int rows, int cols);
    //деструктор
    ~Matrix();

    // ...
};
/////////////////////////////////////////////////////////////////////////////
#endif //defined MATRIX_H

 

/////////////////////////////////////////////////////////////////////////////
// Программирование на языке высокого уровня. Основы языка С++
// Пример 4.4. Класс Матрица
// matrix.cpp
// 
// http://aco.ifmo.ru/el_books/programming
// Университет ИТМО
/////////////////////////////////////////////////////////////////////////////
#include <iostream>
using namespace std;

// подключение описания класса
#include "matrix.h"
/////////////////////////////////////////////////////////////////////////////
// конструктор по умолчанию
Matrix::Matrix()
    : m_rows(0)
    , m_cols(0)
    , m_data(NULL)
{
} 
/////////////////////////////////////////////////////////////////////////////
// полный конструктор
Matrix::Matrix(int rows, int cols)
: m_rows(rows)
, m_cols(cols)
{
    m_data=new double [rows*cols];

    for(int i=0; i<m_rows*m_cols; i++)
    {
        m_data[i] = 0.;
    }
}
/////////////////////////////////////////////////////////////////////////////
//деструктор
Matrix::~Matrix()
{
    if(m_data != NULL)
    {
        delete [] m_data;
    }
}
/////////////////////////////////////////////////////////////////////////////

 

/////////////////////////////////////////////////////////////////////////////
// Программирование на языке высокого уровня. Основы языка С++
// Пример 4.4. Класс Матрица
// test_matrix.cpp
// 
// http://aco.ifmo.ru/el_books/programming
// Университет ИТМО
/////////////////////////////////////////////////////////////////////////////

// подключение описания класса
#include "matrix.h"
/////////////////////////////////////////////////////////////////////////////
void main()
{
    // в момент создания матрицы вызывается конструктор по умолчанию
    Matrix a; 
    // в момент создания матрицы 3х3 вызывается полный конструктор
    Matrix b(3,3); 

    // ...

}// время жизни переменных a и b заканчивается, вызывается деструктор
/////////////////////////////////////////////////////////////////////////////

4.3.3. Проверка правильности параметров. Исключительные ситуации

Конструкторы должны проверять передаваемые им аргументы на корректность значений. Например, показатель преломления не может быть меньше 1. Что делать, если в конструктор были переданы неправильные параметры? Для этого в языке С++ существуют исключительные ситуации.

Класс exception является стандартным базовым классом C++ для всех исключений. Исключения можно сгенерировать в случае возникновения непредвиденной ошибки, например мы предполагаем что при вызове класса Lens никто не будет пытаться задать показатель преломления меньше 1, но при этом такая ситуация возможна, и это может привести к ошибке. Сгенерировать исключительную ситуацию можно при помощи оператора throw:

if(n<1) 
    throw exception("Index of refraction should be greater than 1.");

Для обработки возникшей исключительной ситуации используются try и catch блоки.

В блок try заключается код, в котором предположительно могут возникнуть исключительные ситуации. В нашем случае это вызов конструктора. Кроме того, в этот же блок заключают операторы, которые должны быть пропущены в случае, если исключение возникает. В нашем случае вычисление и вывод на экран параксиальных характеристик не имеет смысл выполнять, если в конструкторе возникла ошибка.

// полный конструктор
Lens::Lens(double r1,double r2,double D,double d,double n)
: m_r1(r1)
, m_r2(r2)
, m_d(d)
, m_D(D)
, m_n(n)
{
    if(n<1)
        throw exception("Index of refraction should be greater than 1.");
    CalculateParaxial();
} 

    ...
    //----------------------------------------------------------------
    // в случае возникновения исключительной ситуации внутри блока try 
    // управление переходит к блоку catch
    try 
    {
        Lens lens7(100., -100., 50., 5., 0.); 
        parax=lens7.GetParaxial();
        parax.write(cout);
    }
    // блок catch - обработка ошибки
    catch(exception& error) 
    {
        // вывод на экран сообщения об ошибке
        cout<<error.what()<<endl;
    }
    ...

Если при выполнение какого-то оператора из блока try возникает исключение – управление сразу переходит к блоку catch. В блоке catch в скобках указывается тип исключения (exception это наиболее общий вид исключения, возможны и другие типы) и имя исключения. Внутри блока catch необходимо обработать ошибку. В нашем случае мы просто выводим на экран сообщение, в каких-то случаях потребуется более сложная обработка. Функция what() содержит текст, сгенерированный в момент создания исключения.

В результаты выполнения данного блока программы на экран выведется сообщение " Index of refraction should be greater than 1.".

Если никаких исключений в try-блоке не происходит, программа игнорирует его catch-обработчик.

Если исключение было сгенерировано, но перехватывание исключения в блоке try не происходило, функция, содержащая этот оператор, немедленно завершается, и программа пытается найти охватывающий try-блок в вызывающей функции. Если нигде в вызывающих функциях не найдется блок try, программа прервется с сообщением об ошибке.