Да, это вариант и неплохой. Здесь мы в очередной раз сталкиваемся с ситуацией, когда в C++ одна и та же цель достигается несколькими способами и наилучший вариант далеко не всегда очевиден. Достоинства const_cast в том, что он "режет глаз", его можно аккуратно инкапсулировать и контролировать, а mutable потенциально влияет на неопределённое количество кода.
Мне кажется, что это рассуждение слишком абстрактно, конкретные задачи могут все поставить на свои места. Главное, чтобы программист знал возможные варианты решения и мог выбрать оптимальные для конкретного случая.
Да, конечно, константность и многопоточность тесно связаны, доступ к константам безопасен из нескольких потоков. Наверное я недостаточно акцентировал на этом моменте, есть только маленький раздел 3.5. Возможно, внесу поправки.
Мне кажется, что константное rvalue (для которых будет выбрана версия const &&) появляется только в одном случае, когда это rvalue является вызовом функции, возвращающей константу. Подобную ситуацию я описал в разделе 5.2.
Для неконстантного контейнера по правилам перегрузки вызовется неконстантная версия этих функций, которые возвращают обычный итератор и вы можете модифицировать элементы контейнера. Для константного контейнера по правилам перегрузки вызовется константная версия этих функций, которые возвращают константный итератор и вы НЕ можете модифицировать элементы контейнера. В C++11 появились сbegin(), сend() и т.д., которые возвращает константный итератор всегда и вы НЕ можете модифицировать элементы контейнера.
Ваши аргументы за левое расположение cv-qual понятны, они базируются на правилах объявления нескольких переменных в одной инструкции. Но как раз эта возможность — объявлять несколько переменных в одной инструкции, — многими авторами относятся к нерекомендуемым. А если объявлять одну переменную в инструкции, то появляются дополнительные аргументы для правого расположения cv-qual. Именно такой точки зрения придерживаются авторы [VJG].
Тот вариант объявления, который вы привели, фактически описан в раздела 4.2. Объявление нескольких переменных в одной инструкции. Правда я не стал приводить описание в общем виде, а описал ситуацию на «на пальцах», но с подробным примером. Все предыдущие описания относились к объявлению одной переменной.
Если static применяется к локальной переменной, то тут вы правы. Но static может применяться к членам класса и к глобальным переменным, в этом случае смысл этого ключевого слова другой. Это кратко описано в разделе 5.2.
Да, виноват, немного поспешил, extern не игнорируется. Даже есть гипотеза, как это работает, но это надо проверять. Сам бы я никогда не написал бы подобный код и тратить время на его исследование особого желания нет.
thread_local и extern можно сочетать в одном объявлении (компилятор и компоновщик не ругаются). Судя по всему (по тултипу и отсутствие ошибок компоновщика) extern в данном случае просто игнорируется. Увы, в C++ довольно много подобных малопонятных с первого взгляда конструкций и их следует избегать. Как писал Герб Саттер не суйтесь в темные закоулку C++, пишите код в котором вы полностью уверены.
Некоторые встроенные типы в С++ используют несколько ключевых слов. Порядок этих слов стандартом не фиксирован и поэтому следующие три объявления объявляют переменные одного и того же типа. unsigned long long d1; long unsigned long d2; long long unsigned d3; Классический синтаксис — это первый вариант, он появляется в сообщениях компилятора и его следует придерживаться, чтобы не запутывать себя и потенциальных читателей кода.
Да, стандартный правильный паттерн реализации присваивания использует идиому копирование-обмен. Сначала создается временный объект с новым состоянием, потом происходит обмен состояний этого временного объекта с исходным объектом и после этого неявно вызывается деструктор для временного объекта, который уже будет содержать состояние исходного объекта. Об этом подробно написано в упомянутой статье про перегрузку операторов.
Для пользовательских классов программист должен сам реализовать присваивание (правда иногда можно доверить генерацию присваивания компилятору). Правильно реализованное присваивание использует деструктор для старого состояния и конструктор для нового, то есть это комплексная операция. Если не использовать деструктор, то мы получить утечку ресурсов. Так что говорить, что вызов деструктора, не относится к операции присвоения мне кажется не вполне корректно.
Правда этот код является антипаттерном, то есть это будет работать, но имеет потенциальные проблемы. Подробнее в разделе 3.9 в моей статье про перегрузку операторов https://habr.com/ru/articles/489666/.
Да, это вариант и неплохой. Здесь мы в очередной раз сталкиваемся с ситуацией, когда в C++ одна и та же цель достигается несколькими способами и наилучший вариант далеко не всегда очевиден. Достоинства const_cast в том, что он "режет глаз", его можно аккуратно инкапсулировать и контролировать, а mutable потенциально влияет на неопределённое количество кода.
Мне кажется, что это рассуждение слишком абстрактно, конкретные задачи могут все поставить на свои места. Главное, чтобы программист знал возможные варианты решения и мог выбрать оптимальные для конкретного случая.
Да, конечно, константность и многопоточность тесно связаны, доступ к константам безопасен из нескольких потоков. Наверное я недостаточно акцентировал на этом моменте, есть только маленький раздел 3.5. Возможно, внесу поправки.
И еще есть семейство set контейнеров, в которых всегда возвращаются константные итераторы, см. раздел 4.2. Там же пример использования const_cast .
Мне кажется, что константное rvalue (для которых будет выбрана версия const &&) появляется только в одном случае, когда это rvalue является вызовом функции, возвращающей константу. Подобную ситуацию я описал в разделе 5.2.
Для неконстантного контейнера по правилам перегрузки вызовется неконстантная версия этих функций, которые возвращают обычный итератор и вы можете модифицировать элементы контейнера. Для константного контейнера по правилам перегрузки вызовется константная версия этих функций, которые возвращают константный итератор и вы НЕ можете модифицировать элементы контейнера. В C++11 появились сbegin(), сend() и т.д., которые возвращает константный итератор всегда и вы НЕ можете модифицировать элементы контейнера.
Если ссылки используются в качестве параметров функции, то она оно позволяет избежать потенциально дорогого копирования.
Большие константные объекты на стеке тоже создают не часто. Чаще всего их располагают в статической памяти.
Поправил.
Спасибо, упустил этот момент. Подумаю, как скорректировать статью.
Ваши аргументы за левое расположение cv-qual понятны, они базируются на правилах объявления нескольких переменных в одной инструкции. Но как раз эта возможность — объявлять несколько переменных в одной инструкции, — многими авторами относятся к нерекомендуемым. А если объявлять одну переменную в инструкции, то появляются дополнительные аргументы для правого расположения cv-qual. Именно такой точки зрения придерживаются авторы [VJG].
Тот вариант объявления, который вы привели, фактически описан в раздела 4.2. Объявление нескольких переменных в одной инструкции. Правда я не стал приводить описание в общем виде, а описал ситуацию на «на пальцах», но с подробным примером. Все предыдущие описания относились к объявлению одной переменной.
Согласен, что термин стандартный возможно не очень удачный. Но этот паттерн должен знать каждый программист
Если
static
применяется к локальной переменной, то тут вы правы. Ноstatic
может применяться к членам класса и к глобальным переменным, в этом случае смысл этого ключевого слова другой. Это кратко описано в разделе 5.2.Да, виноват, немного поспешил,
extern
не игнорируется. Даже есть гипотеза, как это работает, но это надо проверять. Сам бы я никогда не написал бы подобный код и тратить время на его исследование особого желания нет.thread_local
иextern
можно сочетать в одном объявлении (компилятор и компоновщик не ругаются). Судя по всему (по тултипу и отсутствие ошибок компоновщика)extern
в данном случае просто игнорируется. Увы, в C++ довольно много подобных малопонятных с первого взгляда конструкций и их следует избегать. Как писал Герб Саттер не суйтесь в темные закоулку C++, пишите код в котором вы полностью уверены.Некоторые встроенные типы в С++ используют несколько ключевых слов. Порядок этих слов стандартом не фиксирован и поэтому следующие три объявления объявляют переменные одного и того же типа.
unsigned long long d1;
long unsigned long d2;
long long unsigned d3;
Классический синтаксис — это первый вариант, он появляется в сообщениях компилятора и его следует придерживаться, чтобы не запутывать себя и потенциальных читателей кода.
Да, стандартный правильный паттерн реализации присваивания использует идиому копирование-обмен. Сначала создается временный объект с новым состоянием, потом происходит обмен состояний этого временного объекта с исходным объектом и после этого неявно вызывается деструктор для временного объекта, который уже будет содержать состояние исходного объекта. Об этом подробно написано в упомянутой статье про перегрузку операторов.
Для пользовательских классов программист должен сам реализовать присваивание (правда иногда можно доверить генерацию присваивания компилятору). Правильно реализованное присваивание использует деструктор для старого состояния и конструктор для нового, то есть это комплексная операция. Если не использовать деструктор, то мы получить утечку ресурсов. Так что говорить, что вызов деструктора, не относится к операции присвоения мне кажется не вполне корректно.
Вот как можно описать семантику присваивания
X& X::operator=X(const X& src)
{
if (this != std::addressof(src))
{
this->~X();
new (this)(src);
}
return *this;
}
Правда этот код является антипаттерном, то есть это будет работать, но имеет потенциальные проблемы. Подробнее в разделе 3.9 в моей статье про перегрузку операторов https://habr.com/ru/articles/489666/.