С++ для начинающих


Функции-члены со спецификаторами 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 { ... }


Содержание раздела