Каждый раз, когда мы пишем класс, управляющий ресурсами, мы задумываемся о том, что, скорее всего, для него придётся писать move-конструктор и move-присваивание. Ведь иначе объекты такого типа становятся неуклюжими, как std::mutex
, ими тяжело пользоваться на практике: ни вернуть из функции, ни передать в функцию по значению, ни положить в вектор — а если положить его в другой класс как один из членов, то тот класс также «заболевает».
Положим, мы преодолели свою лень (хотя в Rust таких проблем нет!) и садимся писать move-операции для нашего класса. Проблема в том, что move-семантика в C++ имеет фундаментальное ограничение: каждый владеющий ресурсами тип с move-операциями должен иметь пустое состояние, то есть состояние с украденными ресурсами. Его нужно описывать в документации и предоставлять ему поддержку, то есть тратить время и силы на то, что нам не нужно.
Для абстрактных типов данных пустое состояние обычно бессмысленно — если у объекта украли его ресурсы, то он не сможет выполнять свои обычные функции. Но мы вынуждены это делать, чтобы реализовать move-семантику. Для некоторых типов пустое состояние недопустимо: open_file
(в противовес теоретическому file
), not_null_unique_ptr<T>
(в противовес unique_ptr<T>
).
Говоря словами Arthur O'Dwyer, мы заказывали телепорт, а нам дали «вас клонируют и убивают первоначальную копию». Чтобы вернуть себе телепорт, проходите под кат!
Я опишу несколько предложений к стандарту C++, которые объединены одной темой: свести к минимуму число перемещений. Но для начала, ещё раз: почему меня должно это заботить?
- Я не хочу тратить усилия на реализацию move-семантики для всех типов, владеющих ресурсами
- Я не хочу иметь во всех своих типах пустое состояние. Часто оно не к месту. Бывает, что его сложно или невозможно добавить. И всегда это лишние усилия на поддержку
- Даже если move-семантика реализуема, она может быть непозволительна из-за того, что мы хотим раздать указатели на этот объект
- Даже если перемещение допустимо, будет затрачено время на то, чтобы «занулить» первоначальный объект, и потом удалить его по всем правилам. И нет, компиляторы не могут это оптимизировать: раз, два
Итак, поехали.
P1144: Trivially relocatable
Это предложение к стандарту, за авторством Arthur O'Dwyer, добавляет новый атрибут [[trivially_relocatable]]
, которым можно пометить типы, которые можно передавать более эффективно, чем через move. А именно, мы копируем объект на новое место через memcpy
и забываем про первоначальный объект, не вызывая для него деструктор. Правда, таким образом нельзя перемещать локальные переменные, так как компилятор вызывает их деструкторы за нас, не спрашивая, и у этой проблемы нет простого решения.
Атрибут можно применить к вашим классам при их определении. На практике атрибут будет нужен нечасто: компилятор автоматически помечает класс [[trivially_relocatable]]
, если все его члены являются таковыми, и вы не определили кастомные move-конструктор с деструктором (rule of zero). Классы стандартной библиотеки будут помечены [[trivially_relocatable]]
для повышения производительности существующего кода, однако какие именно будут помечены, оставляется на усмотрение реализации. std::vector
и прочие будут использовать новую функцию relocate_at
, которая делает relocation или move, в зависимости от того, что тип поддерживает.
template <typename T>
class [[trivially_relocatable]] unique_ptr { ... };
std::vector<unique_ptr<widget>> v;
for (auto x : ...) {
// Старые unique_ptr перемещаются через relocation, а не move
v.push_back(std::make_unique<widget>(x));
}
С proposal есть несколько проблем, которые обсуждаются:
- Можно пометить класс как
[[trivially_relocatable]]
, даже если его члены таковыми не являются. Например, таким образом можно сломатьstd::mutex
, обернув его в свой[[trivially_relocatable]]
класс - У класса всё равно должен быть реализован конструктор копирования (будем добиваться отмены ограничения)
- Trivially relocatable типы всё равно нельзя передавать в регистрах. Например,
std::unique_ptr<T>
по-прежнему будет передаваться в функции как указатель на указатель
P2025: Guaranteed NRVO
Рассмотренный выше proposal применим тогда, когда объект приходится перемещать, но можно сделать это эффективнее, чем сейчас. Тем не менее, в том случае указатели на объект всё равно «ломаются». В отличие от него, P2025 позволяет устранить саму причину перемещений в некоторых случаях.
C++17 исключил перемещения, когда мы вычисляем значение в return
и тут же возвращаем его. Это называется Return Value Optimization (RVO). P2025 исключает также перемещения, когда мы возвращаем локальную переменную (NRVO). При этом она может быть не-перемещаемой, вроде std::mutex
или наших абстрактных типов данных:
widget setup_widget(int x) {
return widget(x); // OK, C++17
}
widget setup_widget(int x) {
auto w = widget(x);
w.set_y(process(x));
return w; // OK, P2025
}
Кстати, proposal мой :)
P0927: Lazy parameters
Фактически, предлагается аналог @autoclosure
из Swift. Параметр функции может быть помечен специальным образом, чтобы соответствующий аргумент при вызове автоматически оборачивался в лямбду. Перемещение при таком способе передачи параметров не происходит, объект создаётся сразу там, где нужно:
void vector<T>::super_emplace_back([] -> T value) {
void* p = reserve_memory();
new (p) T(value());
}
vector<widget> v;
v.super_emplace_back(widget()); // нет move
v.super_emplace_back([&] { return widget(); }); // под капотом
P0573: Abbreviated lambdas
Это решение более общее, чем предыдущее, и затрагивает также другие проблемные темы. Сокращённый синтаксис лямбда-выражений сделает работу с коллекциями и «ленивыми параметрами» в C++ такой же приятной, как и в нормальных других языках. Правда, с синтаксисом P0573 есть проблемы, но я готов предложить несколько других вариантов, к тому же, более коротких:
// Текущий синтаксис
auto add = [&](auto&& x, auto&& y) { return x + y; };
auto dbl = [&](auto&& x) { return x * 2; };
auto life = [&] { return 42; };
// P0573
auto add = [&](x, y) => x + y;
auto dbl = [&](x) => x * 2;
auto life = [&]() => 42;
// Мой #1: из Rust
auto add = |x, y| x + y;
auto dbl = |x| x * 2;
auto life = || 42;
// Мой #2
auto add = x y: x + y;
auto dbl = x: x * 2;
auto life = :42;
На этом всё! Желаю всем предложениям исправить пробелы и быть принятыми в C++23. Любые вопросы, замечания, пожелания оставляйте в комментариях.
TargetSan
Мне кажется, основная причина в отсутствии destructive move, а не тривиальности перемещения. Но добиться "забывания" старых местоположений по сути невозможно, т.к. это сломает кучу существующего кода. Семантика конструкторов, деструкторов, перемещений, копий и т.п. накручена поверх value семантики из С.
Дополнительная проблема — все эти нововведения призваны ограничивать, а не освобождать. Т.е. чем правильней вы хотите описать поведение типа, тем больше писанины, правил 0-1-3-5-42, атрибутов и т.п. вам надо помнить. А по умолчанию получается POD.
Конечно, "вирусность" нетривиальных копирования и перемещения помогает. Но не сильно. Потому что действие по умолчанию — копирование, перемещение требует отдельного вызова обёртки. Базовых вещей вроде resource wrapper, finalizer в стандартной библиотеке нет до сих пор. Или пишите свой велосипед с риском ошибиться, или ищите в другом месте.
Касательно же Rust, один паттерн оттуда уж точно стоит взять на вооружение. Это copy method, когда создание копии происходит только через явный вызов отдельного метода.
Anton3 Автор
Полностью согласен. Trivially relocatable — калька с Rust, просто поняли, что там намного лучше. Abbreviated lambdas разрабатываются с учётом того, чтобы не копировать неявным образом параметры или результат; про обычные функции там тоже подумали. Было бы круто ещё иметь короткие не-копирующие объявления переменных:
KanuTaH
А в чем смысл? Синтаксический сахар вместо auto или decltype(auto)?
Anton3 Автор
Примерно. С
decltype(auto)
тоже есть проблема, об которую можно споткнуться:decltype(x.y)
выдаст типT
, а неT&
, если полеy
типаT
. Тут спасёт новая конструкция, которая корректно обработает такие случаи.KanuTaH
Но ведь f() в вашем примере в любом случае возвращает некий конкретный тип, не очень понимаю, причём тут проблема с типом полей. Можете пояснить на конкретном примере, какую именно проблему призваны решить эти "короткие не-копирующие объявления", кроме сокращения количества печатаемых символов?
Anton3 Автор
Основная идея — сделать не-копирующие объявления короче копирующих. Второстепенная — корректность:
Причина — неочевидное поведение
decltype
в целом иdecltype(auto)
в частности:decltype(s.x) = int
decltype((s.x)) = int&
Убедитесь сами.
KanuTaH
Да, я в курсе такого поведения, оно вполне себе описанное. То есть, если я правильно понял, что вы хотите, то вы хотите такой универсальный синтаксис, который бы всегда делал автоматический ссылочный тип, но с сохранением CV, что-то типа несуществующего сейчас decltype(auto&&)? Ну, не знаю, городить для этого специальный отдельный синтаксис с ":=" — это такое. Лично я бы предпочел визуально видеть, что я имею дело именно со ссылкой, а тут это неочевидно. Я бы предпочел как раз что-то вроде decltype(auto&&), хотя это и «многабукав», признаю.
Anton3 Автор
Внезапно понял, что
auto&&
сработает. Для возвращаемого типа функции это недопустимо, а для локальной переменной, где надо, сработает lifetime extension. Так что зря я нагородил :facepalm:Хотя если мы потом вернём эту переменную с lifetime extension, то NRVO не сработает. Но тогда уж лучше прописать разрешение на это в NRVO.
KanuTaH
Да, в случае локальной переменной auto&& должно быть вполне достаточно. Вот так вот и делается «синтаксический мусор» © selrorener, хехе.
selrorener
Основания. Хотя глупо это спрашивать — Trivially relocatable — это базовое свойство всего в си. Когда там это было никакого раста в принципе не существовало, но в любом случае я жду оснований. Да и никаких понятий "relocatable" в расте в принципе нет — там голые структуры взятые из llvm(читай си).
Т.е. нет никакие конструкторов, нету наследования, прав доступа, инициализаторов и прочего.
Зачем этот мусор в языке?
Anton3 Автор
В C, но не в C++. Когда при принятии C++11 думали, как уменьшить число копирований, почему-то результатом стала move-семантика. А сейчас обнаруживается, что в 99% случаев-то на самом деле нужен destructive move (или relocation, как его сейчас называют). Хотя в комитете сидят люди далеко не глупые. Значит, со времён C++11 тенденции в языках программирования поменялись. Насколько я знаю, именно Rust привнёс концепцию destructive move в массы.
Relocatable — это понятие из C++. Аналог в Rust называется просто move и записывается как присваивание.
Эта конструкция отменила бы копирование как поведение "по умолчанию". Но я особенно над этим не думал. Возможно, да, фигню сморозил.
selrorener
Я вот не понимаю — откуда вы берёте все эти глупости?
Этого уже достаточно, зачем вы везде пихаете свою второсортную вторичную стриптуху? Что-то потом засыпаться?
К тому же, это так же работает и для С++. Но это не работает для сложных типов, но об этом позже.
Полная и нелепая чушь, опять вы повторяете эту нелепость уже 10 лет. Вы не способны чему-то учиться.
move — это и есть копирование, и копирование всегда было таким. Другое дело, что в C++ с созданием raii копирование стало иметь другую семантику и копирование перестало быть копированием, коим оно является по умолчанию и коим является в си.
В связи с этим и появилось move, которая является тем самым копированием, которое было до раии и которое было в си.
Полная и нелепая чушь. Опять ретрансляция какой-то нелепой пропаганды какой-то нелепой скриптухи. "Trivially relocatable" не имеет никакого отношения к "destructive move" и прочей чуши.
Никакого "destructive move" в принципе не может существовать. Это костыль, который придуман в скриптухах типа раста. Где взята банальная сишная семантика, а далее примитивным статическим анализом поверх тысяч ограничений и ручного труда адептов — была реализована.
Т.е. в скриптухи нету никаких конструкторов, нету никаких деструкторов, нету никакого времени жизни. Кто угодно имеет полный доступ к объекту, как это было всегда в си.
К этому просто прикручена drop-семантика. Схема там проста — когда есть одна ссылка и область видимости кончается — срабатывает drop. Если ссылка не одна — мутировать объект нельзя. drop является мутацией.
Но всего этого не существует где-то за рамками примитивного статического анализатора. На уровне языка это просто структуры из си для которых где-то там вызывает drop. А так — они просто копируются как угодно. Как в си.
Вся эта чушь никак не работает в С++ и не может работать. Где у каждого объекта есть своё время жизни. Поэтому, вместо того, что-бы ретранслировать чушь не разбираясь в вопросе — лучше пойти и изучить букварь.
По поводу "Trivially relocatable" — его смысл в в следующем. Поды можно так же копировать как угодно, как в си. Но если мы навешиваем на этот под конструкторы/деструкторы — просто так копировать что-то уже нельзя. Семантика предполагает их вызов, за редкими исключениями.
Да вот, "Trivially relocatable" — это то, что позволяет сказать. "да, у меня есть конструкторы/деструкторы. Но я позволяю себя копировать, не вызывая их у копий, реализация объекта под хинтом предполагает это и никаких проблем не возникнет".
При этом нужно понимать, что это дырявый костыль. Его область применения крайне ограничена, в основном для всевдо-раишного кода. И подобные решения уже есть. Причём они куда лучше.
Если ещё проще. Подобная схема в рамках раии призвана куда-то наверх засунуть raii-объект, а далее использовать тот же объект как не-раии view. Но здесь можно просто использовать view. С++ это не примитивная скриптуха типа раста, где это невозможно сделать и приходится таскать оригинальные объекты.
Поэтому мы можем использовать raii-объекты как стореджи, а далее использовать view-объекты для доступа. Правда непонятно почему не подходят ссылки — почти везде для данного кейса их можно использовать. Я понимаю раст-скриптуху, где ссылки это муоср неудобный, но в С++ с этим проблем нет. Семантика, конечно же, сложная. Но на то он С++, а не примитивная скриптуха.
Поэтому если не хочется перемещать(читай копировать с вызовом не раишных конструктуоров/деструкторов) raii-объекты и есть гарантия, что изначальный объект не сдохнет быстрее копии. Можно просто сделать ptr_view, в который любой sp может каститься. А он pod — он уже копируется как нужно.
В этой скриптухи вообще никаких понятий нет. Там есть просто беспорядочное копирование чего угодно + все переменные семантически ссылки. move там больше костыль для примитивного статического анализатора.
Почему вам так трудно это понять. В скриптухе просто поды из сишки. Всё. Никаких конструкторов, дестркторов и времени жизни нет. В С++ это есть и уже поверх этого создано raii.
Скриптуха взяла raii из крестов, но оно там совершенно другого вида. Как бы попроще объяснить. В общем, если на примере с си. Это просто указатели, которые можно как угодно копировать. А потом магическим образом где-то компилятор вставить free.
Это примерно как cleanup, только чуть более импрувнутый, что в основном связано с примитивность самого языка и нагрузки на адептов.
Копирование и так поведение по умолчанию. У вас с этим проблемы большие. Есть язык — его логика совершенно иная. Не раишные. Базовая семантика языка другая, а раии это просто некая логика прикрученная сверху.
Ненужно это путать. И ненужно пихать это на уровень языка.
У меня такое ощущение, как я уже говорил, что вы какие-то тайные "засланники". Вы не думая пишите какую-то чушь. Т.е. вы занимаете пропагандой мусорной скриптухи и враньём про то, что пишите.
Допустим, чушь про лямбды. Лямбды в раст-скриптухи — это паста []{} — базового синтаксиса из крестов и руби. Т.е. в скриптуха слишком примитивная — там ненужен список захватов. Точно так же в скриптухи слабая система типов — типов там вообще нет. Поэтому там и ненужны типы.
А вообще подобные примеры — позор:
Я даже не буду говорить о том, что это синтаксический мусор и С++ не тот язык, который помойка. Так же, С++ является языком, а не примитивной скриптухой. Поэтому такой мусор в С++ вообще ненужен.
Antervis
KanuTaH
Оно и далеко не всегда надо, откровенно говоря. Как тут уже было метко подмечено, если ты перелил чай из чашки в желудок, то это не означает, что чашку надо немедленно уничтожить. Зачастую гораздо дешевле просто налить в нее новый чай.
Antervis
по стандарту, после move out от класса можно ожидать только что он будет в «каком-то корректном» состоянии. И т.к. это состояние может не совпадать с ожиданиями, лучше после moved out переменные не использовать. В конце концов, для подавляющего большинства movable классов пара «удаление moved out переменной и создание новой» достаточно дешевые.
Вот мы и приходим к актуальности destructive move
KanuTaH
Их вполне можно использовать для того, чтобы переместить в такую переменную что-то новое, затем проделать некие операции, затем переместить результат куда-то еще, затем опять переместить в нее что-то новое, и так далее — скажем, в нагруженном цикле. Как бы ни было дешево «удаление и создание новой», это все-таки зачастую не бесплатно.
Antervis
в большинстве случаев либо все эти операции (удаление moved out, move constructor/assignment и т.д.) дешевые (дернуть пару указателей), либо дорогие.
А жонглировать инстансами в коде тоже не очень хорошо — можно запутаться какие инстансы живые, а какие — нет.
KanuTaH
В том-то и дело. Вот тут реализованы два цикла:
godbolt.org/z/FkXThD
Первый — это симуляция «destructive move» через использование локальной переменной в теле цикла, второй — оптимизированный, через «повторное использование чашки». Представим, что циклы очень нагруженные. Какой вариант в этом случае предпочтительнее? Обратите внимание, что в первом варианте постоянно идет совершенно лишний вызов деструктора, который может быть недешевым, во втором — делаются только две операции перемещения, и все (ну, по идее, конечно, предполагается, что между ними с foo там должно еще что-то делаться полезное, иначе зачем это все).
mayorovp
Если бы destructive move была и правда destructive — деструктор бы в первом цикле не вызывался.
KanuTaH
Ну вообще да, наверное. Но я еще к тому, что и в текущей реализации перемещения «обращение к moved out» переменной — это не ужас-ужас, а вполне легальная и осмысленная операция, если все сделать аккуратно. Не знаю, можно ли корректно и удобно надстроить «настоящий» destructible move поверх текущей реализации, но если «просто» задепрекейтить обращение в переменной после std::move, то это, боюсь, сломает много кода. Да и в std:move можно передать не только переменную, а, скажем, тот же элемент вектора, и что тогда делать?
mayorovp
Это не столько обращение, сколько повторная инициализация.
Вот с вектором всё уже хуже, да. После перехода на деструктивное перемещение, возможно, придется в подобных случаях вызывать конструктор по умолчанию...
humbug
Это да. Только очень часто из пустой чашки повторно "наливают чай" и пытаются его пить. Хз, какой смысл у этой операции, ведь иногда он приводит к UB.
KanuTaH
— Доктор, когда я делаю вот так, у меня тут болит!
— А вы так не делайте.
humbug
Ну вот компилятор раста и говорит, что так делать не надо)
Видите, у нас с вами полное взаимопонимание!
KanuTaH
Компилятор раста много чего говорит. А потом тот же автор actix'а вынужден его затыкать потому, что компилятор говорит ерунду.
humbug
Это выбор Николая, а не необходимость, вызванная багами компилятора или нерабочей системой типов.
KanuTaH
Ну, на той системе типов, которой располагает раст, ему, видимо, не удалось сделать то, что ему нужно, с той эффективностью, которую он хотел получить. Так что про «необходимость» — это как посмотреть.
humbug
Ну да. Костыльками сделал быстрее, чем любое существующее решение на С++. Дайте время, сделает красиво и без костылей.
KanuTaH
При этом написав в «стиле C», просто на расте, за что его, собственно, и гнобят (я, кстати, не гноблю — я понимаю необходимость компромиссов, и что иногда приходится делать «не так, как советуют лучшие практики»).
humbug
15 раз на проект в десятки тысяч строк кода? Вот уж действительно преступление!
Почему вы в обратную сторону не считаете? Он написал 50к строк кода в стиле Раст и получил производительность лучше, чем все существующие решения?)) Уже не так хорошо звучит для вас, как хотелось бы, да, мистер хейтер?)
KanuTaH
«Преступление», которое ему вменяют ревнители духа раста, как раз и состоит в том, что он по сути обманом отключил механику borrow checker'а, и обеспечивал то, что одновременно будет всего одна мутабельная ссылка на один объект, вручную. Я как раз не считаю это таким уж преступлением, но компилятор уже не мог проверить, что мутабельная ссылка, возвращавшаяся рядом вспомогательных функций — действительно единственная. Я понимаю, что частенько программисту приходится просто доверять, если хочешь получить результат «выше среднего». Фанатики борроу чекера — нет.
humbug
Поэтому я и написал, что это его выбор.
Я еще помню времена, когда в проекте было 150 unsafe. Ну убрали 90% unsafe, проекту от этого плохо не стало, как был на первом месте, так и остался (а не выше среднего). Уберут и оставшиеся, так вы другой проект начнете приводить в пример, как "невозможно писать быстрый раст код без unsafe". Фанатичность она такая, да.
KanuTaH
«Убирание» unsafe там заключалось главным образом в том, что убирался unsafe в описании и в местах вызова функций, написанных в подобном стиле:
По факту и эта функция сама по себе должна была быть объявлена как unsafe, и тот кусок, где она вызывается, тоже должен быть объявлен, как unsafe. Вот те unsafe, которые должны были быть выше, и поубирали, число unsafe упало, но взамен получился unsound код, вся корректность которого держится на гарантиях программиста, что это где-то не вызовется два раза одновременно. Принципиально от убирания верхних unsafe ничего не изменилось.
humbug
Нет, нет, нет, нет, и нет. Лень все пруфы искать.
Жить в выдуманном мире прикольно, но реальность несколько отличается.
KanuTaH
Ну как же «нет», если по первой же вашей ссылке, насколько я понимаю, примерно это и происходит. Что делает эта as_mut()? Она случайно не возвращала сначала «голый» указатель, и была объявлена как unsafe, а потом переписана в стиле, подобном вышеприведенному?
KanuTaH
Хотя да, вроде бы ничего такого там нет, я по крайней мере не нашел. as_mut() — это там из Option. Ну что ж, пока, насколько я понимаю, конструкции типа вышеприведенной предлагают убрать с помощью RefCell, против чего автор сопротивлялся, небезосновательно предполагая, что это отрицательно скажется на производительности. Будем посмотреть.
humbug
Option::as_mut никогда не возвращала голый указатель. Там проблема в ненужном трюкачестве в виде каста ссылки к указателю
as *mut _
, а потом обратно.deviant_9
Права доступа есть. doc.rust-lang.org/reference/visibility-and-privacy.html
Наследования нет, но есть реализация интерфейсов (trait-ов) и композиция, которые решают те же задачи более изящно.
Конструкторов нет, поскольку в отсутствие традиционного наследования задача инициализации объекта корректнее и гибче решается функцией (без параметра self), возвращающей объект. В такой функции можно вычислить значения всех полей до создания объекта, что полностью исключает саму фазу инициализации (в течение которой объект находится в нецелостном состоянии, но виден конструктором и вызываемыми из него методами), а также из неё можно вернуть нечто более сложное, чем просто инициализированный объект (например, монаду вроде Result или Option).
selrorener
Нет, очевидно. Там голые структуры из llvm(читай си). Никаких методов и деструкторов нет. К тому же, заметим, что я не говорил ничего про деструкторы, но адепт уже начал про них что-то рассказывать? Почему? Правильно — он не читал, не думал — он просто ретранслирует пропаганаду.
Есть только drop, который не деструктор. Это что-то типа finalize в жава. Раст это изначально скриптуха с ГЦ, к которой просто накрутили костылей.
Никаких методов нет — есть просто отдельные функции. Перегрузки нет, поэтому взятая из C++-перегрузка превращена в трейты, которые по-сути являются костылями/сахором поверх перегрузки.
Никаких прав доступа, наследования и прочего — нет, очевидно. Инициализаторов полей нет, конструкторов нет. Есть просто списки инициализаций из си.
Роднит их то, что это это и есть структуры си. А тривиально перемещаются они так же, как подобные структуры в C/С++.
Это уже костыль. В связи с тем, что объекты в расте беспорядочно копируются, потому как по природе это скриптуха с ГЦ. Все типы ссылочные, переменные не являются стореджем как в С/С++.
Далее это вызвало проблему, когда для реализация asyn ансейф-хак падал, потому как нужно было брать ссылки, которые умирали.
Опять же, не только.
Это не права доступа. Это костыли из модулей, о которых я ничего не говорил. Я говорил про права доступа для полей.
Опять же, чушь. Ничего они не решают. Ненужно в ответ мне ретранслировать пропаганду локальную — она мне не интересна.
Трейты — это нахлабучка поверх перегрузки. Она никак не влияет на структуру.
Опять пошла пропаганда второсортная. Ещё раз, меня не волнуют сектантские лозунги и за аргументацию они не считаются.
Дополнительные возможности — это следствие. Основное свойство конструктора — это создать класс функций, которые именно создают объекты. А дополнительные возможности — это вторично.
Нет, очевидно. Опять мы видим сектантские лозунги. Никакого гибче нет — это просто отдельные функции инициализации из си. То, что было тысячи лет до того, как появились конструкторы.
Единственная разница, что в этой скриптухе эта функция не просто валяется в глобальном скоупе, а диспатчится по имени. Это типичная инициализация через статические методы, привет жава, привет 95.
Но в основном они нужны потому, что в данной скриптухи нет перегрузки. В жаве так же, поэтому там так же используются статический методы для не-стандартных инициализаций. Очевидно, что это мусор. Непонятно кто, что и куда инициализирует.
В тех же условных наследниках жавы с нормальным дизайном, допустим dart, эта проблема решается именованными конструкторами.
Пошло полное повторение тезисов из методички, которая оправдывает дыры в дизайне этой скриптухи. Насколько же это нелепо.
Всё это ничего не значит. Тут адепты немного перепутали реальность. Пропаганда им внушает, что их реалии — это что-то новое, но нет. Это 70 год. Самый примитив. Поэтому, если каким-то чудом я стану адептом этих лозунгов — я всегда могу использовать статические методы.
Мусорные лозунги. Это было в 70 году в си. Это ничего не значит. Смысл инициализации именно в том, что конструктор и есть атомарный инициализатор.
Всё это нужно в скриптухи, в нормальном языке есть исключения. Я уже давно жду, что-бы какой-то адепт мне это сказал. Посмотрим, получится в этом раз.
К тому же, от создания объекта — мне нужен объект, а не мусор. К тому же, зачем мне перечислять примитивную чушь? Я понимаю, что пропаганда убедила адептов, что это какие-то новшества, но нет. Это то, что существовало ещё до рождения данного адепта.
deviant_9
Методы (не просто отдельные функции) есть.
Деструкторы есть. drop — это именно что детерминированно вызываемый деструктор, после которого столь же детерминированно вызываются деструкторы полей, а никак не «что-то типа finalize в жава».
ГЦ нет. (Давным-давно был, удалили в 2014-м, ещё до выхода первой стабильной версии Rust.) И никаким боком раст не подходит под определение «скриптухи» (ни по достоинствам, ни по недостаткам).
Типы НЕ ссылочные: переменные являются стореджем, как в C++.
В жаве перегрузка есть, в том числе для конструкторов, статические методы для создания объектов там используются по совсем другим причинам (например, для возможности создания объекта производного класса — погуглите «фабричный метод»).
Я ответил только на совсем наглую ложь с вашей стороны (чтобы другие читатели не поверили в неё на слово, а таки затратили пару секунд на гугление). Остальное оставлю без внимания, так как у высказываний настолько недобросовестных собеседников вес всё равно равен нулю.
selrorener
Ой, а чего же так? Адепт призвал братьев по вере, что-бы они мне гадили, при этом проигнорировал все свои нелепые тезисы с которым опозрился? Ой, адепт просто погулил private и кинул первую ссылку в гугле, но опозрился. Ой, а потом просто забил на ответ сославшись на то, что это враньё? Да неужели.
Нету.
Опять какая-то чушь. Зачем адепт ретранслирует нелепые тезисы пропаганды? Деструктор — это то, что привязано к времени жизни объекта. drop никакого отношения к этому не имеет. Это именно finalize из жавы.
Вся эта чушь про какой-то рекурсивный вызов для полей — это просто чушь. Она ничего не значит. И никакого отношения к теме не имеет.
Подождите, но неужели адепт опять врёт? Что же данный адепт невежества цитирует "раст изначально скриптуха с ГЦ", а потом мне отвечает, что ГЦ то был, но сплыл. Т.е. я прав, ведь я не сказал, что он сейчас с ГЦ.
Но это неважно, потому что даже сейчас эта скриптуха с ГЦ. Семантически там везде ГЦ. Просто адепты этой скриптухи не особо компетентны в вопросе, но я им помогу.
Зачем вообще нужен ГЦ? Ой, основная его задача — перекрёстные ссылки. Без этого достаточно и рефкаунта. Но постойте, но ведь раст-скриптуха не может в перекрёстные ссылки. Ой, а почему же?
Таким образом, данное ограничение в скриптухе просто позволяет выкинуть ГЦ и заменить его на рефкаунт. Но ведь пропаганда говорит адептам, что там нет ГЦ? На самом деле она врёт. Там есть ГЦ на rc, но с некоторыми фокусами.
Фокус заключается в следующем. Зачем вообще нужен счётчик? А счётчик нужен для того, что-бы была возможность существования двух ссылок. Но подождите, но ведь с этой скриптухи две ссылки существовать не могут.
Правильно, в этом и заключается фокус. Если ссылка только одна, то счётчик ненужен. Таким образом его можно заменить на, условно, статический счётчик.
Схема простая как топор. Данная скриптуха говорит своим адептам "страдайте за веру" и они страдают, руками проставляя все алиасы. В рамках данной схему любой школьник может написать примитивный статический анализатор.
Схема там простая как топор. В этой примитивной скриптухи есть только ссылки и агрегаты. Со ссылками проблем особо нет. Есть проблемы с агрегатами.
Но там работает следующая схема. Скриптуха говорит адептам "руками свяжи поле и объект", таким образом задача сводится к предыдущей. Т.е. агрегат со ссылкой на какой-то объект считается его алиасом.
А ну ещё есть функция. Эта скриптуха слишком примитивна и не может анализировать тела функций, поэтому адепты так руками проставляют алиасы. Таким образом ссылка за тело функции не может выйти. Если адепт захочет — он может вернуть её из функции, но тогда он обязан связать её со входом. Далее скриптуха понимает, что выход — алиас входа.
Соответственно, сам анализ простой как топор. Есть верхний объект — он создаётся. Далее все ссылки — ссылки. Там неважно копируются они или не копируются. Есть ли у них сторедж, либо нет. Это просто ссылки.
Соответственно, если в каком-то из блоков существует более двух ссылок — ссылки перестают быть мутабельными. Таким образом к ним попросту невозможно применить drop.
Далее, в том блоке(скоупе), где объявлен алиас/ссылка и если она одна — то по выходу из скоупа скриптуха вставляет там drop. Всё.
Т.е. это такая вариация rc статическая. Далее всё остальное — это unsafe-хаки. Т.е. рантайм.
Подходит. Всем.
Нет, к тому же про ссылочные типы я ничего не говорил. Переменные не являются стореджами. Это семантически ссылки. Они всегда ссылки, даже если типизированы как T, а не как ссылка на T. Это базовое свойство скриптухи.
Нет. То, что там есть — это мусор. Оно не применимо r описываемой мною функциональности.
Опять чушь. Кстати, заметим как адепт уже забыл про всю ту чушь, что он нёс про супер-новшества ввиде статически методов в раст-скриптухе. А далее он где-то услышал про фабричные методы, но он опять сел в лужу.
То, что статические методы используются для фабричных методов никак не отменяет тот факт, что они используются для того, о чём писал я. Понятия не имеют зачем адепт несёт подобную чушь. Потому что "в том числе" не значит "только для".
Читаем как "я просто ретранслировал тезисы пропаганды, ответов никаких у меня нет". "я надеюсь на то, что табуны таких же фанатичных и некомпетентных братьев по вере набегут, заминусуют неверного и он перестанет мне писать".
Т.е. ноль равный нулю попытался что-то там поотвечать, но ничего не смог. Это норма для адептов данного учения.
deviant_9
Итак, вы писали: и, ещё раньше: Отвечаю: вот наиболее релевантный фрагмент моей «первой ссылки из гугла», опровергающий ваш тезис про всеобщий доступ к объекту за счёт приватного права доступа для поля:
Занавес.
mayorovp
Вот как раз copy method взять не получится, потому что обратная совместимость. Но это всего лишь принятое соглашение по умолчанию, оно ни на что принципиально не влияет.
А вот "уничтожать" объекты после перемещения можно попробовать. Использование после перемещения и сейчас считается дурным тоном, так что много кода затронуто не будет. Скажем, в C++23 можно объявить использование после перемещения deprecated, а в C++29 или C++32 сделать ошибкой. Если язык до тех пор доживёт.
TargetSan
Copy method как раз несложно внедрить для нового кода. Да и для старого можно, пометив использование копирующих конструкторов как deprecated.
А вот разрушающее перемещение сломает слишком многое. Я уже выше писал, что семантика "владения" в С++ натянута поверх C PODs.
По поводу доживёт или нет — вопрос слишком холиварный. Моё мнение — будет как минимум занимать значимую часть рынка, а то и останется доминирующим. Легаси, база наработанных знаний, рынок спецов, привычки в конце концов.
Overlordff
Ага, после того как выпил из чашки чай (переместил чай из чашки в желудок), её следует уничтожить.