Как стать автором
Обновить

Комментарии 24

Неплохо так. Единственное, что можно добавить в части

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

С++ позволяет делать перегрузки в зависимости от того, через ссылки какого типа мы обращаемся к объекту.

class X
{
// ...
    int GetWeight() &;
    int GetWeight() &&;
    int GetWeight() const&;
    int GetWeight() const&&;
};

Хуже всего то, что работает это ОЧЕНЬ неочевидным образом да еще и по разному, в зависимости от того, как отработал вывод типов. См. пример на godbolt.

У контейнеров такие методы как begin(), end(), rbegin() и rend() каждый имеют константную и неконстантную перегрузку. Таким образом, можно перебирать элементы неконстантного контейнера и изменять их. А можно таким же циклом перебирать элементы константного контейнера, только изменять их уже нельзя.

Если контейнер неконстантный, но ты не изменяешь его элементы, то не совсем ясно, какие методы в этом случае вызываются. Этот момент можно было бы прояснить.

С точки зрения С++ не имеет значения изменяете вы объекты или не изменяете. Сам по себе стандарт С++ вообще понятия не имеет что вы там делаете :)

Он опирается на типы, с которыми вы работаете и на механизм разрешения перегрузок (см overload resolution):

If the expression E has the form PA->B or A.B (where A has class type cv T), then B is looked up as a member function of T. ... The argument list for the purpose of overload resolution has the implied object argument of type cv T.

В то же время функции объявленые с cv-квалификаторами имеют следующее свойство

In the body of a function with a cv-qualifier sequence, *this is cv-qualified

Собирая эту информацию воедино, получаем, что при вызове begin() на любом неконстантном векторе, по правилам С++ всегда надо вызывать не-const перегрузку этого метода, вне зависимости от того, что мы с этим объектом делаем.

Для неконстантного контейнера по правилам перегрузки вызовется неконстантная версия этих функций, которые возвращают обычный итератор и вы можете модифицировать элементы контейнера. Для константного контейнера по правилам перегрузки вызовется константная версия этих функций, которые возвращают константный итератор и вы НЕ можете модифицировать элементы контейнера. В C++11 появились сbegin(), сend() и т.д., которые возвращает константный итератор всегда и вы НЕ можете модифицировать элементы контейнера.

А ещё есть const_cast и его помощники - функции std::move и std::as_const.

И еще есть семейство set контейнеров, в которых всегда возвращаются константные итераторы, см. раздел 4.2. Там же пример использования const_cast .

Это читерство и антипаттерны.

Если некий тип задуман как состоящий из константной (ключевой) и неконстантной частей, то лучше эти неконстантные части объявить как mutable.

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

const_cast, снимающий константность - это всегда звонкий колокол.

Да, это вариант и неплохой. Здесь мы в очередной раз сталкиваемся с ситуацией, когда в C++ одна и та же цель достигается несколькими способами и наилучший вариант далеко не всегда очевиден. Достоинства const_cast в том, что он "режет глаз", его можно аккуратно инкапсулировать и контролировать, а mutable потенциально влияет на неопределённое количество кода.

Мне кажется, что константное rvalue (для которых будет выбрана версия const &&) появляется только в одном случае, когда это rvalue является вызовом функции, возвращающей константу. Подобную ситуацию я описал в разделе 5.2.

Константные переменные для оптимизации кода это хорошо, только это работает за счёт того, что изменение константной переменной это UB.

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

Справедливости ради, обычно нет причин использовать ссылки/указатели на константы, вместо самих констант и основное предназначение const именно гарантии неизменяемости в API.

Если ссылки используются в качестве параметров функции, то она оно позволяет избежать потенциально дорогого копирования.

Не влияет никак... Давай пофантазируем.

Есть два метода с одним параметром, одинаковый тип параметра, только первый с обычной ссылкой, второй с константной. Можно ли включить оптимизацию для второго метода?

Какую именно оптимизацию? Их много разных.

В общем случае - разницы никакой. Возможно в ряде частных случаев, где компилятору доступно много контекста, например, это inline функция и мы туда передаём переменную со стека, адрес которой гарантированно не передаётся "непойми куда" в extern функцию - да, возможно компилятор сможет чего-то наоптимизировать. Хотя с вероятностью близкой к 100% он сможет эту оптимизацию применить и к обычной ссылке.

Правда, динамические константные объекты можно отнести к большой экзотике, какой-нибудь полезный пример привести трудно.

Элементарно! Если объект достаточно велик, чтобы жить на стеке.

Большие константные объекты на стеке тоже создают не часто. Чаще всего их располагают в статической памяти.

Некоторое время назад сделал std::shared_ptr<const some_data> для случая, когда экземпляры some_data разделялись сразу между несколькими потребителями. Грубо говоря, один тред выкачивает данные из источника и формирует std::shared_ptr<const some_data>, после чего этот shared_ptr отсылается всем заинтересованным потребителям (каждый на своем треде). За счет того, что данные константные не нужно париться о том, что кто-то из потребителей начнет их менять.

Да, конечно, константность и многопоточность тесно связаны, доступ к константам безопасен из нескольких потоков. Наверное я недостаточно акцентировал на этом моменте, есть только маленький раздел 3.5. Возможно, внесу поправки.

доступ к константам безопасен из нескольких потоков

Наличие mutable членов структур/классов уже делает картину мира сложнее. Не говоря уже про то, что в C++ нет транзитивной иммутабельности (к счастью или к сожалению), поэтому даже вызов константного метода у константного объекта может вести к изменению каких-то данных:

class mutator {
public:
  void change_something() {...}
  ...
};

class viewer {
  mutator * m_mutator;
public:
  viewer(mutator * mutator) : m_mutator{mutator} {}

  void f() const { m_mutator->change_something(); }
};

mutator m;
const viewer v1{&m};
const viewer v2{&m};

v1.f(); // Значение m могло измениться.
v2.f(); // Значение m могло измениться еще раз.

Так что не все так однозначно, к сожалению.
Но лучше уж иметь такой const, чем не иметь никакого.

Большое количество больших константных объектов в принципе не создают в статической памяти.

А если эти константы времени исполнения?

Например, строки. Например, содержимое конфиг-файла.

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

Мне кажется, что это рассуждение слишком абстрактно, конкретные задачи могут все поставить на свои места. Главное, чтобы программист знал возможные варианты решения и мог выбрать оптимальные для конкретного случая.

Это уже отмазки пошли. Я сказал, зачем нужно делать new const T, вы сказали, что чаще бывает static T, я привёл сценарии - более чем рабочие! - а вы отделываетесь какой-то совершенно пустой фразой "главное, чтобы программист знал..."

Ну вот пусть программист и знает, что константы на куче размещать можно и нужно.

Низкий поклон тебе, человек!

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории