All streams
Search
Write a publication
Pull to refresh
63
0
Дмитрий Пономарев @dm_frox

Программист

Send message

Ваши аргументы за левое расположение 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/.

int typedef a;
Это то же самое, что
typedef int b;
Последний вариант это классический синтаксис. Первый вариант это некие допустимые вариации, которых в C++ довольно много и их лучше не использовать, чтобы не запутать себя и потенциальных читателей кода.

Да.

И еще

std::string s{ "meow" };

std::string s = { "meow" };

auto s = std::string("meow");

auto s = std::string{ "meow" };

Все эти варианты вызывают один и тот же конструктор.

Присваивание применяется к ранее созданным объектам и поэтому включает обязательный этап — освобождение ресурсов этого объекта. Конечно, для простых переменных типа int ничего освобождать не надо, но для более сложных, то том числе и для std::string, это делать обязательно. А вот при инициализации этого делать не нужно, так как мы инициализируем пустой объект, то есть инициализация более простая процедура. При инициализации работает конструктор объекта а при присваивании оператор присваивания, это разные сущности (хотя и связанные).

Полностью согласен.

Здравствуйте,
Вызов ::operator new(size) — это вызов стандартной функции выделения памяти, которая в качестве параметра принимает требуемый размер. Эта фунция только выделяет память, ничего больше.
Вызов ::new(size) не будет компилироваться, так как в этом случае синтаксически правильной формой должна быть ::new X(/* аргументы конструктора */), где X — это имя класса. В данном случае ::new — это оператор new, который не только выделяет память, но и вызывает конструктор, то есть инициализирует объект.
С уважением. Дмитрий Пономарев.

Спасибо, рад, что мои материалы оказались полезными.

Спасибо за комментарий, очень образное описание сути интерфейса. Многие языки программирования поддерживают интерфейсы на уровне языка (например C#, Java), но вот C++ нет. Приходится эмулировать интерфейсы с помощью чисто виртуальных функций, но при этом возникают достаточно много тонкостей и потенциальных проблем. В своей статье я как раз и попытался разобраться с этими проблемами.

Вообще надо различать вывод аргумента шаблона и вывод типа параметра функции. Они могут не совпадать, типа параметра функции может быть украшен спецификатором ссылки, const и другими модификаторами. В шаблонах функций аргумента шаблона нам доступен, но в лямбдах с auto он будет скрытым.

Спасибо за конструктивную критику.

А вот использовать ссылку в качестве параметра функции безопасно

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

И про "ссылку" получаемую из итератора std::vector<bool> ...

Согласен, что итераторы, возвращающие прокси-объекты, не обсуждал. Эта тема подробно обсуждается у Скотта Мейерса.

Не сказано так же о том, что взятие ссылки на временный объект продлевает его срок жизни до конца жизни самой ссылки.

Не согласен, этому посвящен раздел 4.1.

Одна из идей, которой руководствуются при проектировании ссылок – это сделать ссылку максимально неотличимой от объекта, на который она ссылается. Этим можно объяснить, почему при инициализации C++ ссылки не используется специальный оператор (типа & в случае указателей) и не используется оператор разыменования (типа * в случае указателей) для доступа к объекту. В результате возникают некоторые коллизии, но небольшие, например, нельзя перегрузить функции, у которых параметр передается по значению и по ссылке на константу.
Теперь про неизменяемость C++ ссылок, то есть отсутствие нулевых ссылок (обязательная инициализация) и невозможность перенаправить на другую переменную. Посмотрим, какие изменения в языке нужны, если бы ссылки были изменяемые, то есть допускали нулевое значение и перенаправление на другую переменную. Первая проблема – это как быть с присваиванием. Нужно отличать присваивание самих ссылок и присваивание объектов, на которые она ссылается. Для изменяемых ссылок логично реализовывать присваивание как присваивание самих ссылок (а как иначе организовать перенаправление?), но присваивание объектов тоже очень важно в C++. Вторая проблема – это использование самих ссылок в качестве выходного параметра (это еще один вариант реализовать перенаправление). Для этого надо было бы разрешить указатели на ссылку и ссылку на ссылку. Решение всех этих проблем усложнило бы и без того непростой C++, то есть неизменяемость C++ ссылок позволило не переусложнить язык.
Теперь посмотрим, что происходит в языках со сборкой мусора, например C#. Объекты, управляемые сборщиком мусора, доступны только через ссылку, таким образом, никаких коллизий между объектом и ссылкой на него быть не может, не нужен оператор разыменования. Ссылки на объекты, управляемые сборщиком мусора, являются изменяемыми, то есть могут быть нулевыми и при присваивании происходит присваивание самих ссылок. Но при этом сами объекты не поддерживают присваивание, для них нельзя перегрузить оператор =, поэтому коллизий не возникает. Если для объекта требуется операция, аналогичная копированию или присваиванию, то в C# надо реализовать специальные методы, типа IClonable.Clone(). Но потребность в таких операциях возникает крайне редко. Ссылки на объекты, управляемые сборщиком мусора, сами могут быть выходными параметрами функции, для таких параметров при объявлении и вызове надо использовать ключевое слово ref.

Information

Rating
Does not participate
Location
Санкт-Петербург, Санкт-Петербург и область, Россия
Registered
Activity