Pull to refresh

Comments 57

UFO just landed and posted this here

Автоматически скачивать что-то при компиляции просто при упоминании -- это же готовый бекдор. А если библиотек с одними именем несколько? Не выдумывайте костыли, делайте как в Rust (и вероятно много где в современных языках) -- библиотеки явно указываются в файле проекта, система сборки их скачивает из реестра. Есть дефолтный реестр, и можно явно указывать, из какого брать.

Вообще, мне кажется, вы опять частично переизобретаете Rust:

  1. Семантика перемещения там по умолчанию, чтобы получить семантику копирования, туп должен явно реализовать типаж Copy

  2. При передаче в метод ссылки на объект это нужно явно указывать на вызывающей стороне (исключение -- если переменная и так имеет тип "ссылка", то никаких дополнительных значков при ее передаче не нужно. Т.е. все-таки только по тексту вызова не понять, может функция модифицировать переменную или нет).

  3. Авторазрушение объектов после последнего использования -- для начала ознакомьтесь с не лексическими временами жизни (Not Lexical Lifetimes, NLL) в Rust. Тут сильно много неочевидных моментов. Автоматически уничтожать переменные после последнего использования -- сильно неочевидно. С чего вы взяли, что в примере кода

    f = open('output.cpp', 'w')
    f.write('int main() {}')
    os.system('g++ output.cpp')
    

    после os.system нужно в f вызвать финализацию? А если типом f будет mutex_guard? Будите отпускать его прямо перед выполнением защищаемой им операции?

Почему реализацию аллокатора памяти лучше делать на уровне компилятора, а не через глобальные переопределяемые функции или операторы, как это сделано в C++?

Аллокатор -- это лишь интерфейс. Подсвеченная вами проблема только лишь в интерфейсе, который не возвращает реальный выделенный размер. Если его добавить в интерфейс, то проблемы нет.

Гарантированная оптимизация хвостовых вызовов -- пожалуй единственная здравая идея, которая еще ни в одном из мейнстримных языков не реализована. Только нужно более строгие гарантии дать -- гарантировать оптимизацию хвостовой рекурсии с аккумулятором. Тогда компилятор сможет даже рекурсивное вычисление последовательности Фибоначчи в цикл свернуть.

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

В 11l это легко понять: если стоит квалификатор &, значит функция может модифицировать переменную. Если не стоит квалификатора — значит не может.

после os.system нужно в f вызвать финализацию? А если типом f будет mutex_guard?

Для этого в 11l присутствует два вида деструкторов.

С чего вы взяли, что в примере кода ... после os.system нужно в f вызвать финализацию?

Деструктор f (который закрывает файл) будет вызван сразу после f.write('int main() {}'). Или что вы имеете в виду под финализацией?

Деструктор f (который закрывает файл) будет вызван сразу после f.write('int main() {}'). Или что вы имеете в виду под финализацией?

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

перед os.system нужно в f вызвать финализацию?

А ещё необходимо фиксировать версии библиотек - иначе, программы написанные пару лет назад собрать будет нереально, из-за необходимости угадывать версии. В идеале вообще нужно сделать так, чтобы можно было подключать одну и ту же библиотеку разных версий одновременно, например

From awesomelib-1.1.0 import foo

From awesomelib-2.1.1 import bar

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

Ну, можно ведь как-то помечать модули в реестре и автоматически скачивать и устанавливать при компиляции только модули с пометкой trusted.

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

Если вы имеете в виду то, что при попытке обращения в коде к модулю с именем скажем backdoors_collection компилятор скачает и установит этот модуль без ведома программиста (а иначе увидев модуль с таким странным именем программист хорошо подумает, прежде чем устанавливать его), так это решается вот этой самой пометкой trusted, ибо никакой модуль с таким подозрительным именем [даже если в нём и не будет вредоносного кода] такую пометку не получит.

Т.е. думать ‘всё равно ’|надо|‘ в любом случае’. Просто есть два варианта:

  1. Думать каждому программисту, безопасно ли подключать/устанавливать данный модуль/библиотеку.

  2. Или думать только модератору реестра [или совету модераторов] ставить пометку trusted добавленному в реестр модулю или не ставить [очевидно, что по умолчанию trusted стоять не должно].

И тут, я считаю, второй вариант будет даже безопаснее.

А вообще суть идеи с автоустановкой модулей/пакетов в том, чтобы поощрять экспериментирование и упрощать обмен кодом: увидел фрагмент кода на Stack Overflow — вставил в свою программу и запустил (при этом не нужно ни лезть в начало исходного файла и дописывать в import имена модулей, которые используются в этом фрагменте кода, ни устанавливать эти модули).

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

Так я ж не против. Указывать это всё можно (как я и написал в статье: "Если требуется не последняя версия пакета/модуля, это можно указать в файле конфигурации/сборки проекта." [внутри спойлера "Немного технических подробностей"]).
Речь о том, чтобы указывать это [какие модули необходимы программе] было не обязательно.

В MAM реализовано нечто похожее. Вместо trusted флага - репозиторий просто включается в общий реестр. Репы далее фрактально наследуют доверие. Например, если ввёл имя $ya_morda_api, то это значит, что управляющий корневым репозиторием доверяет организации ya, которая доверяет команде проекта morda, которая опубликовала библиотеку api. При этом выбирая тот или иной корневой репозиторий ты выражаешь своё доверие его управляющему. Вплоть до форка, чтобы доверять только себе.

Версий, соответственно, тоже никаких нет.

У разных людей разные предпочтения того, что считать trusted. Уверен, вы слышали про людей, которые не доверяют гуглу или майкрософту. Эта пометка, которую только вы можете поставить в своем проекте, а не какой-то дядя-владелец репозитория.

(при этом не нужно ни лезть в начало исходного файла и дописывать в import имена модулей, которые используются в этом фрагменте кода, ни устанавливать эти модули)

Автоматически добавить импорты может и IDE, незачем это встраивать в компилятор. Экспериментирование поощряется наличием playground-ов, где все самое важное уже импортировано.

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

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

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

Экспериментирование поощряется наличием playground-ов, где все самое важное уже импортировано.

  1. Не встречал ни одного playground-а, который бы импортировал хоть что-то неявно [т.е. сверх того, что явно импортируется в коде на странице playground-а]. По вашей же ссылке на play.rust-lang.org не заметно, что "всё самое важное уже импортировано". Один из наиболее продвинутых playground-ов — Repl.it — умеет автоматически устанавливать пакеты при попытке их импортирования в коде (т.е. import markdown приведёт к автоустановке этого пакета). Считаю, что аналогичное поведение компилятора, установленного локально, вполне допустимо.

  2. Для полноценного экспериментирования как правило необходима возможность чтения файлов [текстовых, а иногда и двоичных], что поддерживается далеко не всеми playground-ами (многие playground-ы не поддерживают даже консольный ввод). И даже если и поддерживается, часто нужна достаточно высокая производительность (к примеру, требуется найти какие-то закономерности в большом наборе текстов).

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

Необязательность тоже можно ограничить. Когда программа/модуль/библиотека выкладывается в какой-то реестр/‘публичный репозиторий’, к ней предъявляются определённые требования (наличие лицензии, номер версии, указание зависимостей и прочее), но на этапе экспериментирования можно минимизировать количество этих формальностей [и только небольшое количество кода преодолевает этап экспериментирования и используется кем-то кроме его разработчиков].

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

А разве сейчас видно, кто использует какой-то пакет npm или модуль Python за пределами этого же реестра [npmjs.com или pypi.org в данном случае]? Я имею в виду, в каких проектах на GitHub/GitLab/Bitbucket/SourceForge он используется.

В любом случае, пользователей библиотек гораздо больше, чем разработчиков библиотек. И почему бы не упростить [хоть немного] жизнь пользователям?

Но все вышесказанной просто меркнет по сравнению с одной банальностью -- у вас все равно в любом проекте зависимости от конкретных версий библиотек.

Нет, не в любом. У меня довольно много скриптов [на Python], состоящих всего из одного файла. Причём порой даже над именем файла я не особо задумывался (а у некоторых вместо названия просто число — порядковый номер этого скрипта).
И есть много публичных библиотек, которые уже давно "устаканились" и вообще практически не обновляются — при их использовании нет смысла указывать номер версии.

Python просто не позволяет выразить это намерение явно средствами языка

if val := dic.get(key):
     print(val)

или

with suppress(KeyError):
    val = dic[key]
    print(val)

И ещё: автоматическое скачивание модуля при использовании - это очень, очень плохо. Как насчёт version pinning / dependency resolution / указания репозитория, откуда скачивается? Это лишь малые проблемы, которые сходу приходят в голову.

Первый пример не до конца корректный. Если в словаре лежит не truethy значение - например False или какой нибудь response 404, то результат может быть неверный. Даже явная проверка на is not None может не помочь в случае, если в словаре лежит как раз None.
Второй пример более удачный, но может подавить лишние ошибки, если под этим менеджером контекста будет много кода.

В общем как по мне - явная поддержка такой конструкции в языке кажется неплохой идеей.

Здесь как обычно используется проверка на конкретный объект:

MISSING = object()

dic = {}
key = ...

if (val := dic.get(key, MISSING)) is not MISSING:
  ...

Но правда говоря, в большинстве случаев проверка на None - более чем.

Ну и да, правда говоря, если иметь возможность вернуть именно ссылку/указатель, то проверка на присутствие пары ключ-значения становится проще.

Согласен, пример рабочий. Но это уже выглядит не очень элегантно. Поддержка на уровне языка мне все еще нравится =)

Удивительно, что до сих пор никто не увидел ошибку в вашем комментарии.
Для очевидности [в чём ваша ошибка], обернём код из статьи в функцию и добавим тестовый вывод для проверки корректности изменений кода:

dic = {1: 'a', 3: 'b', 4: 'c'}

def f(key):
    if key in dic:
        dic[key] += '...'

f(1)
f(2)
f(3)
print(dic)

А теперь попробуйте переписать этот Python-код таким образом, чтобы поиск key в dic внутри функции f всегда осуществлялся только один раз.

def f(key):
    with suppress(KeyError):
        dic[key] += '...'

Моё замечание [о наличии ошибки] относилось только к первому фрагменту Python-кода в вашем комментарии.
Признаюсь честно, второй фрагмент [с suppress] я даже не заметил. :)(: Или просто не воспринял всерьёз.
И вот почему.
Весь смысл задачи ‘чтобы поиск key в dic всегда осуществлялся только один раз’ заключается в повышении производительности этого кода. Чтобы не делать [второй] лишний поиск key в dic. Ваше же решение с suppress в данном контексте просто не имеет смысла [и всерьёз его воспринимать нельзя]. Порождение исключения при каждом обращении к отсутствующему ключу в словаре — это очень-очень дорого. [Лишний поиск key в dic обойдётся гораздо дешевле.]
Вот простенький бенчмарк, который показывает в 5 раз меньшую производительность кода с suppress [по сравнению с кодом с лишним поиском key в dic]. Но в любом компилируемом языке программирования разница будет гораздо больше.

  1. ask forgiveness, not permission некоторыми считается каноническим питоном. Но нужно использовать с умом: если ожидается, что чаще всего значения не будет, то исключения, конечно, добавят оверхед, и желательно их избегать.

  2. Вообще считаю неуместным говорить про скорость в питоне - он не быстрый по определению. У вас вон в цикле конкатенация строк, я ж не жалуюсь :) Поэтому я считаю, что писать нужно максимально простой и читаемый код (и если suppress позволяет этого достичь - то не вижу причин не использовать его только из-за гипотетического оверхеда). Если видим, что тормозит - тогда да, начинаем оптимизировать или берём какой-нибудь numpy / rust.

С передачей аргументов интересная тема. Исторически передача без квалификаторов означала передачу по значению через стек, хотя на самом деле передача по ссылке или еще как-то ничуть не хуже. Ваша идея отказа от неявного синтаксиса передачи сложных объектов и явного требования указания квалификаторов довольно интересна. Кстати, а почему "copy" а не какой-нибудь спецсимвол типа "@" ? Можно вообще составить список возможных способов передачи и проанализировать, что там есть.

UFO just landed and posted this here

Справедливости ради, {} при выражении сложной логики действительно превращаются в мусор, потому что единственный способ сделать читаемый код - это использовать отступы. А если мы уже используем отступы, чтобы не получить write-only код - почему бы не избавиться от дублирующей сущности?

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

Мне достаточно удобно выделять, потому что всё равно я буду перемещать курсор к концу блока, чтобы поставить или убрать скобку. Удобный вам сценарий должен решаться добавлением хоткея "увеличить/уменьшить отступ всего блока". И {} опять не нужны.

Что отступы что скобки не дают нормальной читаемости при сколь-нибудь большом коде.

Наиболее наглядное решение из тех что я видел выделение блоков цветом фона и отступами.

Не всегда, иногда наоборот, лучше написать конденсированный код (чем разряженный), в котором с первого раза будет проглядываться структура. Обычно при этом нужно еще и табличное форматирование соблюсти, потому и не люблю всякие автоформаттеры, которые этого не понимают и наоборот, превращают все в кашу.

В IDEA автоформаттер — просто золото. Практически всё можно настроить, в том числе и выравнивание "друг под другом".

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

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

Потому что читабельный код это в том числе текст, который соответствует правилам дизайна текста.
Я не дизайнер, но мне кажется очевидным, что различные избыточные скобки, закорючки и прочие спецсимволы затрудняют восприятие текста. Если можно писать без скобок (так, что это не вредит пониманию кода), то лучше писать без скобок. Если можно писать and вместо &&, то лучше писать and. Если ты не математик, который в формулах, как рыба в воде, то такие вещи для тебя действительно визуальный мусор, затрудняющий восприятие.
Не думаю, что во времена создания C о таких вещах задумывались. (А ЕЩЕ В ТЕ ВРЕМЕНА СЧИТАЛОСЬ ХОРОШИМ ТОНОМ ПИСАТЬ ПРОГРАММЫ КАПСОМ, ДУМАЮ, СЕЙЧАС ДАЖЕ ДАЛЕКИЙ ОТ ДИЗАЙНА ЧЕЛОВЕК ПОНИМАЕТ, ЧТО ЭТО НЕ ОК)
А в наше время при создании языка нужно уделять большое внимание текстовому дизайну.
Потому что код пишется один раз, а читается много.

А, вы знаете, кое с чем не согласен :)
Как раз буквы складывать в слова нам приходится учиться с детства. А чтобы различить несколько изображений особый навык не нужен. Именно выделяющиеся символы вроде && мозгу намного проще выцепить из потока текста, нежели чем "and", сливающееся с остальным кодом. Чем больше символов — тем проще ориентироваться. Тем более, сейчас появились лигатуры!

В CИ-подобных языках используются специальные символы для операторов, которые не используют в именах структур, что на первом же ментальном шаге отделяет одно от другого. Код все-таки не совсем текст - разные его элементы имеют разный приоритет при просмотре в зависимости от ситуации.

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

Отказ от избыточных фигурных скобок и точек с запятой улучшает восприятие текста, а их использование даёт возможность записать код более компактно. Лучше предоставить программисту выбор: скобки допустимы, но не обязательны.

f x = y * z 
  where
  y = x + 1
  z = x - 1

g x = y * z 
  where { y = x+1; z = x-1 }

Использование в операторах и идентификаторах разных наборов символов не только улучшает восприятие кода, но позволяет определять собственные операторы

infixl 0 |>
x |> f = f x

f list =
  list 
  |> filter (>0)
  |> map (*2)
  |> foldr (+) 0
  
g list = list |> filter (>0) |> map (*2) |> foldr (+) 0

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

Лучше не надо. Синтаксис должен быть максимально однозначным. Чтобы на совести линтера поменьше всего было.

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

но позволяет определять собственные операторы

Тоже лучше не надо. Очень усложняет чтение кода. Постоянно будешь задаваться вопросом А что автор имел в виду?

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

Идея с явным копированием и автоматическим перемещением не нова. В Rust такое есть.

Лично я при этом не сторонник явного вызова копирования. Ручной вызов clone как-то напрягает. По этой причине в моём языке программирования копирование работает через = для любых типов одинаково.

32-битные указатели на 64-битной платформе - идея хорошая. Только проблема возникает во взаимодействии с системными библиотеками и любыми другими библиотеками, не поддерживающими 32-битные указатели. Они ведь могут возвращать полноразмерные указатели. Как их тогда хранить? Иметь в языке два отдельных типа указателей - короткий и полноценный? Вроде в C такое проходили в эпоху перехода с 16 на 32 бита.

На Linux, кстати, был x32 ABI, но его по не очень понятным мне причинам похоронили. Возможно как-раз из-за таких проблем.

На Linux, кстати, был x32 ABI, но его по не очень понятным мне причинам похоронили. Возможно как-раз из-за таких проблем.

Тратятся ресурсы на сборку пакетов под этот ABI, а им никто не пользуется. Решили что нерационально поддерживать.

Вроде в C такое проходили в эпоху перехода с 16 на 32 бита.

Это было не в эпоху перехода, а в эпоху 16 бит. Тогда, напомню, в ходу была идиотски сделанная сегментная адресация, из которой и следовала необходимость двух типов указателей - "ближнего", содержащего только смещение, и "дальнего", содержащего ещё и селектор сегмента.

Она не идиотская только от того, что вы её не поняли.

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

Она идиотская из-за того, что 4 гигабайта виртуального адресного пространства отображаются всего на мегабайт реального.

То что вы описываете — это real mode (реальный режим) работы процессора. Если вы телепортировались из 1981-го года, 80286 еще не выпущен, а есть только 8086 с его 20-битной адресной шиной (1 Мб адресуемой физпамяти), то это единственный возможный режим работы процессора. Тогда он еще не назывался real mode, потому что никаких других режимов попросту не существовало.

Но если вы пишите хотя бы из 1983-го, то уже выпущен 80286, а значит в нем есть защищенный режим (пока еще только 16-битный), а значит есть дескрипторы сегментов и дескрипторные таблицы, а значит есть возможность произвольного маппинга между сегментами и физической памятью, объем которой теперь ограничен не 1 Мб, а 16 Мб, потому что шина адреса увеличена до 24 бит с 20-ти. Произвольного — значит определяемого программистом, системным программистом, а не жесткой формулой, как в реальном режиме

Размер сегмента пока еще не может быть больше, чем 65536 (просто потому что процессор пока еще умеет только в 16 бит), но имея 16 Мбайтное пространство физической памяти, прикладной программе/задаче доступно виртуальное адресное пространство размером аж 1 Гб.

Hidden text

Какой в этом смысл, если, имея максимум лишь 16 Мб физпамяти, этот гигабайт виртуального адресного пространства можно лишь заполонить кучей копий (проекций) одних и тех же регионов 16 Мбайтной физпамяти?

А большой в этом смысл, поскольку наличие Present-бита в дескрипторе сегмента и соответствующего прерывания/исключения (при обращении к сегментам с P=0) дарует системным программистам (ОС-писателям) возможность реализовать сваппинг.

Только это был не страничный сваппинг (как привычно сейчас), а сегментный: выгружался/подгружался целый сегмент целиком.

В ранних версиях Windows вроде Win3.1 этот механизм использовался.

То есть при желании можно создать (инициализировать) до 16K сегментов размером до 64K каждый. Подчеркиваю: не пересекающихся сегментов (как в реальном режиме). 16K*64K=1G.

Помимо этого, у 80286 даже в реальном режиме адреса не урезались до 20 бит (шина-то теперь 24-битная), что породило такое явление, как HMA.

А потом в 1985-м году появился 80386, который мог все то же, что и 80286, но вдобавок к защищенному режиму появился 32-битный режим.

А значит каждый сегмент теперь мог иметь размер до 4 Гб (раньше был до 64K), а количество сегментов, которыми прикладная программа могла бы пользоваться, по прежнему составляло 16K, что дает виртуальное АП размером 64 Тб.

Размер шины адреса был увеличен до 32 бит, что давало до 4 Гб физпамяти (позже выпущенные црезанные версии 386SX в рассмотрение не берем).

По-прежнему отображение между 48-битным полным адресом (виртуальным) и 32-битным физическим адресом могло быть произвольным (задаваемым программистом). Только теперь был введен дополнительный уровень: виртуальный 48-битный адрес транслировался в 32-битный линейный, а затем 32-битный линейный в 32-битный физический.

С выключенной страничной организацией линейный был тождественен физическому. С включенной: преобразовывался через дерево PDBR->PDE->PTE.

А потом появился PAE и размер физических адресов был увеличен до 36 бит.

Зачем вы говорите о сегментах так, как можно было говорить только по состоянию на 1981-й год? Особенно учитывая то, что в 8086 сегментные регистры хоть и были, но самих сегментов как одной из фич защищенного режима — не было. Сегменты интересны именно в контексте защищенного режима — это же по сути высокоуровневый буфер с аппаратным контролем выхода за границы и аппаратным контролем доступа. Это возможность отдельные процедуры и микроблоки кода поместить в изолированные песочницы, такие, что если в них и есть какие-то уязвимости, пощволяющие завладеть управлениеи — сам факт получения управления хакерским кодом не давал бы ровно ничего: из условной функции подсчета md5-хеша большого объема данных просто некуда было бы джампнуть, чтобы сделать что-то нехорошее, да и никаких других данных такая задача просто бы не видела.

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

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

Но я всё ещё считаю глупой сегментную адресацию 8086го процессора ("эпохи 16 бит")

И нет, в 1983м эта адресация не закончилась, я упирался в её ограничения даже в 2004м.

Эпоха 16 бит включает в себя эпоху 16-битнонго защищенного режима с полноценными сегментами и ОСями типа ранних 16-битных Windows.

Но, в отрывк от этого: если считаете глупой схему трансляции адресов реального режима в физические, то хочется спросить — а как бы вы сделали?

Я бы разделил 16 бит селектора не как 4+12, а как 8+8. То есть умножал бы его не на 16, а на 256.

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

Выглядит так что сломает кучу старых программ. Будет вызываться другой код, который работает как-то по другому.

RVO/NRVO и обязательный RVO в C++17 также ломает часть логики завязанную на вызов копирования.

Но ничего, все довольны.

Определение "простого типа" который можно быстро копировать зависит от платформы (например наличие AVX2), и нужно выбирать из 2х зол - или убитая производительность или куча не переносимого кода.

Голосом старушки, архетип вечно недовольной уборщицы:

А потому что все делають, делають всякое C++, а что делають и сами не знають. Распустилась молодежь, раньше было memcpy и static char BUF[256] на все случаи жизни, а сейчас срамота!

Нет уж, спасибо. static char BUF[256] - это один из главных типов уязвимостей в программах. Отчасти из-за этого некоторые регулирующие органы уже советуют не использовать C там, где требуется хоть сколько-нибудь безопасности и надёжности.

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

Речь о РОН или о SIMD? Пример такой оптимизации можно?

Очередной ликаидатор с++. Сразу после раста

что фактически не имеет смысла, т.к. делает лишнее копирование объекта типа std::set<int> при передаче его в функцию f

обычно - да, в среднем типичном случае вы правы и это ошибка.

Но этот код имеет смысл, если функция принимает множество, внутри его изменяет и выбрасывает потом, при этом исходное множество не должно изменяться. практично ли это? может быть нет. "фактически не имеет смысла" - нет, не согласен, смысл есть хотя это и corner-case

Но этот код имеет смысл, если функция принимает множество, внутри его изменяет

Как же она его изменяет, если это const std::set<int>? :)(:
Без const — да, имеет смысл (хоть и редко).

Резюмируя, в C++03 существует 4 способа передачи аргумента типа std::set<int> в функцию:

void f(std::set<int> st);
void f(std::set<int> &st);
void f(const std::set<int> st);
void f(const std::set<int> &st);

И вот третий из них — смысла не имеет.

const на аргументе который копируется - не часть abi, она не меняет сигнатуру функции, только указание компилятору что не стоит таки разрешать менять объект. Всегда есть отвратительная опция const_cast сделать.

Кроме того могут быть mutable члены, хотя std::set наверное не имеет их.

В хидере метод может быть определен с const на аргументе и без него в реализации. И наоборот. Конст на аргументе переданом по копии не часть интерфейса функции

Да, можно много как этот const обойти, но зачем его писать-то?

Sign up to leave a comment.

Articles