Инфопульс Украина corporate blog
Programming
C++
Compilers
Comments 27
+3
С одной стороны, задавать подобные вопросы на собеседовании — дело бесполезное, это почти ничего не говорит о способностях интервьюируемого.
Все бы так собеседования проводили :)
+1
Определение mutable в классе зачастую излишне раскрывает внутреннюю жизнь класса. Если вы делаете класс для внешнего API, то изменения его внутренней структуры, той же технологии кеширования, как в статье, заставляет пользователей вашего API перекомпилировать свои программы.
Поэтому мне больше по вкусу скрытая реализация внутренних данных, как это использовано в Qt.
class ObjectPrivate;

class QObject {
private
    QObjectPrivate *d;
public:
// ... any const methods
};
+1
И тёмные стороны PIMPL имеет. Для стабилизации ABI подходит как нельзя лучше, но лишним разыменованием и использованием кучи, омрачает славный перформанс C++. Что в раз очередной сподвигает нас на предварительный анализ задачи и выбор правильных инструментов для её решения.
0
Использовать кучу правило или не использовать выбираете вы. Примеров есть кучи, где d на структуру, в контейнере выделенную один раз, указывает.

Не, пусть Йоде будет йоднутое. Короче, вас никто не обязывает выделять на каждый объект отдельную структуру из кучи. В стеке, конечно, выделить так память не получится, но можно заранее отложить память в объекте-контейнере/фабрике под N объектов.

Что да, в очередной…
0
Только если для внешнего API.
В остальных случаях это только утруднит написание и ухудшит производительность кода.
Оба подхода нужны. Я просто считаю что такие решения должны приниматься из практических соображений, а не быть делом вкуса.
0
Поэтому мне больше по вкусу скрытая реализация внутренних данных, как это использовано в Qt.
Возможность изменения реализации без перекомпиляции — это же совсем другая проблема, здесь не рассматриваемая.

А решение проблемы спецификаторов const в публичном API на QObject, придётся так или иначе передать классу QObjectPrivate, и там снова думать — использовать mutable, или что-то другое.
0
Вы хорошо подумали?
QObjectPrivate, во-первых, не публичный, его структура скрыта, во-вторых, не обязан иметь const методов.
0
Я почему-то подумал, что внутри const-метода нельзя вызывать не-const методы по указателю, который описан в классе:
// QObject.h

class QObject {
        class QObjectPrivate;
        QObjectPrivate *d;
public:
        int area() const;
};

// QObject.cpp

class QObject::QObjectPrivate
{
public:
        int area() { return 0; }
};

int QObject::area() const
{
        return d->area(); // вот здесь вызов не-const метода
}

Но этот пример у меня скомпилировался.

Другое дело, если бы QObjectPrivate был не указателем, а членом класса QObject.
// QObject.h

class QObjectPrivate
{
public:
        int area() { return 1; }
};

class QObject {
        QObjectPrivate d;
public:
        int area() const;
};

// QObject.cpp

int QObject::area() const
{
        return d.area(); // вот здесь ошибка
}
Я думал, что поведение должно быть аналогичным.
+1
Да, в вызове d->area() значение указателя, лежащее в QObject, не меняется, следовательно для компилятора константность QObject соблюдена.
Еще обратите внимание, что во втором случае, когда QObjectPrivate является членом класса QObject, вам пришлось перенести его определение в .h, т.е. раскрыть детали его реализации пользователям QObject. Что не всегда желательно.
+3
Не знаю, кем mutable забыт, но мне иногда приходится его использовать.
0
для многопоточных данных\флагов — и сам делал, но вот как часто кто-то добавляет мутабельности константным методам класса?
+1

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

0
А не будет ли это UB?
Если я сниму const с класса через const_cast, и вызову его не-const метод.
+2
Только если у исходного объекта, у которого вызывается метод стоит квалификатор const. Если объект не константный, то его можно будет изменить через const_cast даже внутри const метода.
0
Интересно. Вот в таком примере:
int GLOBAL;

struct point 
{ 
        int x;

        int calc() const {
                GLOBAL = x*17;
                change();
                return x*17;
        }

        void change() const {
                point* self = const_cast<point*>(this);
                self->x++;
        }
};

компилятор не может полагаться на то, что значение 'x' у const-объекта *this не будет изменено при вызове ф-ции change()?

В общем-то, играясь с разными опциями компилятора мне не удалось заставить его выполнить common sub-expression elimination, т.е. умножение на 17 он всегда честно делает 2 раза, независимо от опций оптимизации.
-7
Есть мнение, что const — довольно бесполезная, а местами даже вредная штука.

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

И наоборот, захотев добавить const на функцию, вы вынуждены обвешивать const'ами все что она вызывает. Причем в процессе вам может понадобиться потрогать код сторонней библиотеки — страдайте и обмазывайтесь const_cast'ами и mutable'ами.

Теперь про бесполезность. const не дает никаких гарантий вызывающему коду, поэтому непонятно зачем он в сигнатуре.

А не дает никаких гарантий потому что const имеет право изменять:

1. Аргументы. Среди которых (возможно через множество промежуточных звеньев) может оказаться this, только уже без const'а

2. Статические переменные. Среди которых опять же может оказаться this.

3. mutable, рассмотренный в этой статье.

4. И вообще, можно же взять и memset зафигачить. Что-то где-то поменяется.

5. Самое печальное, что были прецеденты когда это непредумышленно происходило. И ложная надежда «const значит this не поменяется» только мешала.
+3
const нужен в качестве:
1. дополнительной проверки корректности программ компилятором. Да, грязные хаки могут менять состояние константного объекта, но в корректном коде они не нужны и практически отсутствуют (за исключением mutable примитивов синхронизации). А вот очевидные ошибки/опечатки будут своевременно отловлены.
2. дополнительной информации об API для вызывающей стороны.
3. перегрузка по const/non-const позволяет проводить некоторые оптимизации.
Причем в процессе вам может понадобиться потрогать код сторонней библиотеки — страдайте.

для этого и существуют mutable/const_cast. Зловредную библиотеку можно вызывать через const-корректный адаптер
-2
1. Я вам целый список предоставил, способов которыми объект может измениться в результате вызова const-метода. Из них грязным хаком я бы назвал const_cast. Но вообще-то все эти механизмы входят в стандарт и UB, например, не вызывают.

2. Какой дополнительной информации? Вы точно прочитали текст на который отвечаете?

3. О, спасибо что напомнили. Запишите это тоже в минус: необходимость дублирования кода для const/не-const бесит, приводит к ошибкам, часто содержит в себе const_cast или является темплейтом (что привносит свои, темплейтные проблемы)

4. Она не зловредная. Она просто не расставила const везде где это возможно (и никто этого не делает, вы в том числе). Хотя бы просто потому что у нее не было инструмента который бы это сделал.
+4
1. использование любого из этих методов не по прямому назначению подходит под правила дурного тона и является легитимным основанием отклонить ваш код на ревью. memcpy в с++ практически никогда не нужен.
2. например, если в библиотеке есть функция, принимающая const T &val, она (с поправкой на профпригодность автора) не будет менять val.
3. к каким ошибкам?
4. всегда пишу const-корректный код. За последний год использовал const_cast единожды (и то в вызове сишной библиотеки). Мозг, глаза и руки — достаточный набор инструментов для написания корректных программ.

Я уже не говорю про то, что иногда const-корректность необходима для достижения желаемого поведения

п.с. есть еще noexcept, который, между прочим, может оптимизировать выполнение некоторых операций. Его вы тоже не рекомендуете к использованию только потому, что лень печатать?
+2
Статические переменные. Среди которых опять же может оказаться this.

Можно пример того, как this оказывается статической переменной?
+1
Указатель со значением, равным this, вполне может лежать в статической переменной. Думаю, именно это и имелось в виду.
0
Понятно. Напоминает старый анекдот:
Обитатель одной квартиры подал жалобу, что в его окно можно увидеть голых женщин в бане через дорогу. Приходит инспектор:
— Да что Вы такое говорите, ничего не видно!
— А возьмите бинокль и залезьте на шкаф!
+1
Впечатления от таких вопросов и ответов у меня двоякие. С одной стороны, задавать подобные вопросы на собеседовании — дело бесполезное, это почти ничего не говорит о способностях интервьюируемого

При написании const-корректных и многопоточных программ, mutable рано или поздно (скорее рано) понадобится. На мой взгляд, претендент на позицию с++ миддла не обязан знать все тонкости с++11/14/17, но базовые вещи из с++03 знать обязан
0
Имхо, mutable нужно редко, но в тех законных случаях, когда да — без него не обойтись.
0
Я бы отнес mutable (как и const_cast, memset, raw new, и многое другое) к разряду «грязных хаков». Да, ими вполне можно и даже нужно пользоваться, но относительно редко, и, как правило, лишь для цели достижения максимальной производительности и лишь в некоторых служебных местах. Поэтому любое их использование должно быть надежно инкапсулировано.
Only those users with full accounts are able to leave comments., please.