Как стать автором
Обновить
61
0
Дмитрий Пономарев @dm_frox

Программист

Отправить сообщение

Да, это вариант и неплохой. Здесь мы в очередной раз сталкиваемся с ситуацией, когда в 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/.

Информация

В рейтинге
Не участвует
Откуда
Санкт-Петербург, Санкт-Петербург и область, Россия
Зарегистрирован
Активность