Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Липпман.doc
Скачиваний:
11
Добавлен:
14.08.2019
Размер:
7.54 Mб
Скачать

13.3.4. Специальные функции-члены

Существует специальная категория функций-членов, отвечающих за такие действия с объектами, как инициализация, присваивание, управление памятью, преобразование типов и уничтожение. Такие функции называются конструкторами. Они вызываются компилятором неявно каждый раз, когда объект класса определяется или создается оператором new. В объявлении конструктора его имя совпадает с именем класса. Вот, например, объявление конструктора класса Screen, в котором заданы значения по умолчанию для параметров hi, wid и bkground:

class Screen {

public:

Screen( int hi = 8, int wid = 40, char bkground = '#');

// объявления других функций-членов не изменяются


};

Определение конструктора класса Screen выглядит так:

Screen::Screen( int hi, int wid, char bk ) :

_height( hi ), // инициализировать _height значением hi

_width( wid ), // инициализировать _width значением wid

_cursor ( 0 ), // инициализировать _cursor нулем

_screen( hi * wid, bk ) // размер экрана равен hi * wid

// все позиции инициализируются

// символом '#'

{ // вся работа проделана в списке инициализации членов

// этот список обсуждается в разделе 14.5


}

Каждый объявленный объект класса Screen автоматически инициализируется конструктором:

Screen s1; // Screen(8,40,'#')

Screen *ps = new Screen( 20 ); // Screen(20,40,'#')

int main() {

Screen s(24,80,'*'); // Screen(24,80,'*')

// ...


}

(В главе 14 конструкторы, деструкторы и операторы присваивания рассматриваются более подробно. В главе 15 обсуждаются конвертеры и функции управления памятью.)

13.3.5. Функции-члены со спецификаторами const и volatile

Любая попытка модифицировать константный объект из программы обычно помечается компилятором как ошибка. Например:

const char blank = ' ';


blank = '\n'; // ошибка

Однако объект класса, как правило, не модифицируется программой напрямую. Вместо этого вызывается та или иная открытая функция-член. Чтобы не было “покушений” на константность объекта, компилятор должен различать безопасные (те, которые не изменяют объект) и небезопасные (те, которые пытаются это сделать) функции-члены:

const Screen blankScreen;

blankScreen.display(); // читает объект класса


blankScreen.set( '*' ); // ошибка: модифицирует объект класса

Проектировщик класса может указать, какие функции-члены не модифицируют объект, объявив их константными с помощью спецификатора const:

class Screen {

public:

char get() const { return _screen[_cursor]; }

// ...


};

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

class Screen {

public:

bool isEqual( char ch ) const;

// ...

private:

string::size_type _cursor;

string _screen;

// ...

};

bool Screen::isEqual( char ch ) const

{

return ch == _screen[_cursor];


}

Запрещено объявлять константную функцию-член, которая модифицирует члены класса. Например, в следующем упрощенном определении:

class Screen {

public:

int ok() const { return _cursor; }

void error( int ival ) const { _cursor = ival; }

// ...

private:

string::size_type _cursor;

// ...


};

определение функции-члена ok() корректно, так как она не изменяет значения _cursor. В определении же error() значение _cursor изменяется, поэтому такая функция-член не может быть объявлена константной и компилятор выдает сообщение об ошибке:

error: cannot modify a data member within a const member function

ошибка: не могу модифицировать данные-члены внутри константной функции-члена

Если класс будет интенсивно использоваться, лучше объявить его функции-члены, не модифицирующие данных, константными. Однако наличие спецификатора const в объявлении функции-члена не предотвращает все возможные изменения. Такое объявление гарантирует лишь, что функции-члены не смогут изменять данные-члены, но если класс содержит указатели, то адресуемые ими объекты могут быть модифицированы константной функцией, не вызывая ошибки компиляции. Это часто приводит в недоумение начинающих программистов. Например:

#include <cstring>

class Text {

public:

void bad( const string &parm ) const;

private:

char *_text;

};

void Text::bad( const string &parm ) const

{

_text = parm.c_str(); // ошибка: нельзя модифицировать _text

for ( int ix = 0; ix < parm.size(); ++ix )

_text[ix] = parm[ix]; // плохой стиль, но не ошибка


}

Модифицировать _text нельзя, но это объект типа char*, и символы, на которые он указывает, можно изменить внутри константной функции-члена класса Text. Функция-член bad() демонстрирует плохой стиль программирования. Константность функции-члена не гарантирует, что объекты внутри класса останутся неизменными после ее вызова, причем компилятор не поможет обнаружить такую ситуацию.

Константную функцию-член можно перегружать неконстантной функцией с тем же списком параметров:

class Screen {

public:

char get(int x, int y);

char get(int x, int y) const;

// ...


};

В этом случае наличие спецификатора const у объекта класса определяет, какая из двух функций будет вызвана:

int main() {

const Screen cs;

Screen s;

char ch = cs.get(0,0); // вызывает константную функцию-член

ch = s.get(0,0); // вызывает неконстантную функцию-член


}

Хотя конструкторы и деструкторы не являются константными функциями-членами, они все же могут вызываться для константных объектов. Объект становится константным после того, как конструктор проинициализирует его, и перестает быть таковым, как только вызывается деструктор. Таким образом, объект со спецификатором const трактуется как константный с момента завершения работы конструктора и до вызова деструктора.

Функцию-член можно также объявить со спецификатором volatile (он был введен в разделе 3.13). Объект класса объявляется как volatile, если его значение изменяется способом, который не обнаруживается компилятором (например, если это структура данных, представляющая порт ввода/вывода). Для таких объектов вызываются только функции-члены с тем же спецификатором, конструкторы и деструкторы:

class Screen {

public:

char poll() volatile;

// ...

};


char Screen::poll() volatile { ... }