Константные методы

Рейтинг: 14Ответов: 3Опубликовано: 01.04.2011

Просветите, пожалуйста, на тему константных методов в C++; зачем нужны, в чем преимущества?! С небольшими примерами кода, если не затруднит.

Ответы

▲ 18Принят

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

class A
{
  int a;
  int b;
  public:
  int getA() { b = b + 1;return a; }
  int getB() { a = a + 1;return b; }
};

Здесь мнемоника и семантика немного несоответствуют. Вот хочется думать, что если

A x, y;
...
bool f = x.getA() == y.getA() && x.getB() == y.getB();

f истина, то x и y в чём-то похожи, но внутренности класса реализованы не так. Пользователь класса должен быть уверен, что геттер ничего не испортит. Хороший разработчик класса должен явно описывать характер поведения метода с помощью const или отсутствия const

class A
{
  int a;
  int b;
  public:
  int getA() const { return a; }
  int getB() const { return b; }
};

Но если вам вдруг понадобится делать прогу, в которой есть что-то вроде подсчёта обращений к переменной, то семантика геттеров заставляет употребить const, но счётчик обращений нужно увеличивать. Для этого случая есть слово mutable! Этим словом мы пометим поля, которые вроде как бы члены класса, но они не отражают что ли его состояние

class A
{
  int a;
  int b;
  mutable int cntA;
  mutable int cntB;
  public:
  int getA() const { cntA++; return a; }
  int getB() const { cntB++; return b; }
};
▲ 4

Несколько слов о применении константных методов. Классы с константными методами удобно применять:

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

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

▲ 4

В нестатические методы класса компилятор неявно передаёт константный указатель на объект от которого был вызван метод, его сигнатура

T* const this   // адресс на который указывает this поменять нельзя

благодаря ему можно связать обращение к переменным и методам с фактическим адрессом экземпляра. Следовательно код:

void f()
{
   m_a = 10;
}

эквивалентен

void f()
{
   this->m_a = 10;
}

В случаи с константными методами, вы меняем два поведения:

  1. Теперь данный метод может быть вызван для константного объекта.

    const Foo a;
    a->f();         // only if f() is const method 
    
  2. Теперь внутрь данных методов передаётся константный указатель на константу.

    const T* const this;  // адресс и состояние объекта на который указывает this поменять нельзя
    

следовательно

void f()
{
   this->m_a = 10; // ошибка нельзя менять состояние константного объекта
}

Преймущества константного метода:

  1. можно вызывать для константных объектов и указателей на константу.
  2. применения grammar const (всё что должно быть const - должно иметь данный спецификатор) - если метод не меняет объект то почему бы программисту явно не указать это себе, другим программистам и компилятору?
  3. Можно перегрузить метод через его константность:

    struct Array
    {
        int x;
        int operator[]( int index ) const
        {
            return x;
        }
        int& operator[]( int index )
        {
            return x;
        }
    };
    
    int main()
    {
        Array a;
        a[1] = 10;          //  int& operator[]( int index )
                            //  ok
        const Array b;
        b[1] = 10;          //  int operator[]( int index ) const
                            //  error: `int` is not lvalue
    }