const-методы и способы борьбы

Const mantra

Побывав на собеседованиях, я обратил внимание на то, что достаточно ходовым является вопрос о const методах класса. Казалось бы: "Ну что тут может быть сложного?". Действительно, ничего. Но, как показал блиц-опрос, затруднения у некоторых возникают...

Ниже я рассмотрю что такое const методы, зачем они нужны и отвечу на вопрос, который вытекает из определения.

Что такое const-метод?

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

Есть некоторый класс Foo, метод doSomething() которого присваивает переменной члену _x значение 5.

class Foo
{
public:
    Foo(): _x(0) {}
    void doSomething();

private:
    int _x;
};

void Foo::doSomething()
{
    _x = 5;
}

Ничего нового, все обычно. Верно? Но если этот метод пометить как const:

class Foo
{
public:
    Foo(): _x(0) {}
    void doSomething()const;

private:
    int _x;
};

void Foo::doSomething()const
{
    _x = 5;     // для const-метода изменение атрибутов запрещено
}

То компилятор ругнется на строку, и напишет (в случае gcc):

test.cpp: In member function ‘void Foo::doSomething() const’:
test.cpp:15:10: error: assignment of member ‘Foo::_x’ in read-only object

Но все это просто и не так интересно как следующее.

И что, никак таки не изменить?

Именно этот вопрос ставит многих не совсем опытных программистов в затруднение. Однако, как не странно, меня он вовсе не поставил, хотя я и не перечисляю себя к опытному брату-программисту. Об этом писали и Мейерс, и Лафоре, и Страуструп.

На данный момент мне известно 2 способа преодолеть защиту, которую накладывает const-метод. "Если можно это преодолеть, зачем тогда нужны эти const-методы?" — спросите вы. Ответ прост: преодолевать эту защиту не надо, это считается дурным тоном и плохой практикой. Но все же способы существуют и знать о них было бы не плохо.

Что такое mutable?

Мало кто знает, но в C++ есть ключевое слово mutable, которое может ставиться перед атрибутами класса (естественно, кроме констант и статических атрибутов). Атрибут помеченный как mutable может изменятся из const-методов.

class Foo
{
public:
    Foo(): _x(0) {}
    void doSomething()const;

private:
    mutable int _x;
};

void Foo::doSomething()const
{
    _x = 5;     // все впорядке! :)
}

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

const_cast<> или путь самурая

Вторым способом является применение великого и могучего const_cast<>. Применяя его можно легко выстрелить себе в ногу, но все же он существует и от этого никуда не денешься. Суть const_cast<> сводится к манипулированию константностью объекта: мы можем добавлять ее и снимать. Например так:

class Foo
{
public:
    Foo(): _x(0) {}
    void doSomething()const;

private:
    int _x;
};

void Foo::doSomething()const
{
    const_cast<Foo*>(this)->_x = 5;
}

Скотт Мейерс в одной из глав своего бестселлера пишет, что применяя const_cast<> можно избавится от дублирования кода в разных версиях функций. Но это уже другая история.

Note

Стоит заметить, что const_cast<>, не смотря на свое название, позволяет манипулировать спецификатором volatile.

Вместо заключения

Использование mutable выходит боком: атрибут может изменятся из любого const-метода класса. Использование const_cast<> — это грязный хак. Но знать об этом, как C++ программисту, стоит. Избегайте применения этих трюков. Если это по каким либо причинам надо сделать — задумайтесь, а правильную ли архитектуру вы выбрали?