А вообще, это так не работает. Если архитектура призвана жрать много электричества, её будут ставить туда, где можно жрать много, и ничего за это не будет.
В телефонах ARM потому что её можно подключить к аккумулятору, и она всегда будет там лучше, чем x86.
Раз вы задаёте этот вопрос, значит вы используете подход "отдельная директория для public хедеров", как и в этой статье. Ну т.е. когда все публичные заголовочные файлы физически перемещаются в отдельную директорию.
В их папках обычно не создаётся CMakeLists просто потому, что с ними особо нечего делать, кроме как скопировать на этапе install (лично я советую делать это и на этапе сборки, особенно если есть генерируемые хедеры, и использовать их ТОЛЬКО из build-директории, а не из source-директории, но это вопрос отдельный). Раньше копировали как могли, потому что довольно давно существующее свойство таргета PUBLIC_HEADER не сохраняет структуру директорий для публичных хедеров, и потому им проще не пользоваться вовсе.
НО! В версии 3.23 наконец приехал стандартный механизм, который реально работает для публичных хедеров - file sets. Единственный на данный момент поддерживаемый тип file set-а это как раз HEADERS. Для этого типа даже автоматически изменяется INCLUDE_DIRECTORIES таргета. Ну и самое главное - при install(TARGETS)сохраняется относительная структура файлов и директорий - как раз то, что обычно нужно для публичных хедеров.
Есть ещё холиварный вопрос - использовать ли директиву file(GLOB) или все файлы всегда перечислять в CMakeLists. Лично я пользуюсь глобом, но у него много противников.
Кстати, почему include, а не add_subdirectory? С инклудом у вас же не будет зеркалироваться структура дерева исходников в билд-дерево.
установка разных конфигураций (static/shared, Debug/Release/..) в разные директории (тогда ничего специального делать не нужно)
Мне кажется это самый разумный и единственный работающий подход. Спасибо Conan-у за то, что именно такой путь они и выбрали, по сути стандартизировав сборку в разных конфигурациях. Все остальные велосипеды жутко усложняет работу.
Тот же GenerateExportHeader генерирует экспортный хедер, который вполне готов к использованию и в STATIC-варианте. В случае размещения только одной конфигурации в одной директории, никакие дополнительные ухищрения не нужны.
Кстати, поддержка мультиконфигурационности в самом CMake - тоже не сахар. Она сделана по сути только для нескольких генераторов, причём прежде всего для IDE вроде Visual Studio, и сильно усложняет код скриптов кучей одинаковых переменных, но для разных конфигураций. Мне кажется гораздо правильнее реализовать такое по append-принципу "несколько прогонов cmake-скрипта -> один солюшен/проектный файл", чтобы новые конфигурации просто "дописывались" в уже сгенерированный проект. Но имеем что имеем.
Спасибо за статью! Отсутствие best practices в документации самого CMake - это, пожалуй, его главная проблема сегодня. Даже синтаксис - и тот можно стерпеть.
А в том, что у них самооценка ниже плинтуса. Потухшие глаза и явное нежелание как-то напрягаться.
Мне кажется здесь нарушена логическая цепочка. Почему, на ваш взгляд, если у человека занижена самооценка, он не желает напрягаться? Разве не наоборот?
Почему спрашиваю: вы установили входной фильтр, а такое решение не может быть необдуманным. А я к сожалению не смог нарисовать портрет человека, который должен идти мимо ещё на этапе фильтрации соискателей в форме поиска.
После прочтения таких статей, ради самоуспокоения всегда напоминаю себе о том, как возросли наши требования как пользователей:
все разучились управлять информацией. Никто больше не ценит простые инструменты, вроде иерархической файловой системы в целях создания порядка. Тех, кто раскладывает музыку во FLAC-е по сотням папочек, считают душными дедами. Все пользуются поиском на стриминговых сервисах.
ладно, не все разучились. Кто-то не умел с самого начала, и вряд ли бы научился. Ведь мы пригласили пользоваться информационными технологиями своих родителей, своих друзей. Им нужны более интуитивные интерфейсы, анимации, подсказки.
теперь мы рассчитываем на информационные технологии ГОРАЗДО сильнее, чем ранее. Многие уже забыли, что такое BSOD и фарш на файловой системе просто при наборе текста в Word. Согласитесь, даже ненавистная Винда сегодня куда более стабильная, чем её древние предки 95/98/Me.
многие вещи, вроде уже упомянутого Юникода, воспринимаются как должное. А за это тоже нужно платить сложностью. Тот же протокол TLS, который по меркам 90-х - супернавороченная вещь уровня дорогущего банковского ПО. А сейчас мы боимся даже картинку скачать по HTTP, вдруг куки/токены угонят.
Ну т.е. если подумать, у софта было достаточно причин стать сложнее.
Конечно, ситуация с JS, вебом и этими вашими Электронами - это другое (c). Это просто одна немаленькая корпорация выиграла битву за программную платформу, и мы пожинаем плоды этой победы. Надеюсь WebAssembly когда-нибудь свергнет JS с должности платформы, "под которую лучше писать по-умолчанию, чтобы дотянуться до максимального числа пользователей". Мне кажется, это самое большое легаси во всём IT на данный момент.
Добавлю ещё про ключик Update Build Revision: HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\UBR . Оттуда как раз можно получить второе число из OS Build, которое как раз и выдаётся командой ver. Это же число видно и в окне System > About в панели управления.
Instra сегодня сделала рассылку о своём решении по поводу текущих событий. Выдержка по существу:
Please note the following:
We will no longer be accepting new registrations and inbound transfers of .RU Domains.
Existing customers will be able to continue to auto-renew their existing .RU domains when they expire, subject to any changes in sanctions and our ability to maintain supply. We will inform you if in future we can no longer process renewals. Early renewals or multi-year renewals will not be accepted. Please make sure to set those domains you wish to be renewed to renew at expiration.
Windows Containers, на использование которых можно и Docker переключить (если конечно он у вас запущен на той версии винды, которая уже поддерживает контейнеры).
Конечно отсутствие строгой типизации не приводит к реальным багам. Отсутствие строгой типизации ПОЗВОЛЯЕТ программисту добавлять новые баги.
Во-первых, я думаю вы понимаете, что если у вас есть JSDoc и тесты, то статическая типизация у вас УЖЕ есть, только неявно и контроллируется она вручную. То, что вы умеете и можете это делать средставами, удобными именно вам, не значит что ваше решение объективно лучшее.
Во-вторых, ошибки на этапе компиляции — это, пожалуй, объективно полезно. Да, вы можете сказать, что тесты вы запускаете также в процессе сборки проекта и можно сказать что это эквивалентное средство. Но если я могу достичь аналогичного результата с помощью информации, которая обрабатывается И компиляторо И программистом одинаково (синтаксис строго-типизированного языка), зачем мне эту информацию разделять между документацией для разоработчика (JSDoc) и тестами?
В третьих, я думаю тесты — это замечательная вещь, которой можно и нужно покрыть всё, что НЕ УДАЛОСЬ покрыть более продвинутыми проверками, такими как типизация (и прочие виды проверок в compile-time). Иными словами, я считаю мы должны идти от тестов к более специализированным и строгим средствам верификации, а не наоборот.
Давать программисту сначала свободу в виде динамической типизации всех переменных и значений, а потом в большинстве случаев эту свободу «забирать», ограничивая тестами РЕАЛЬНЫЕ ветви исполнения кода и РЕАЛЬНЫЕ типы данных, которые код может обработать — это неэффективно. А в реальном коде именно так и будет происходить — не так уже часто нужен действительно «полностью динамический» тип (top-type или any).
И да, я до сих пор не понимаю как форсировать контракты в большой команде разработчиков на динамическом языке. В каждой функции проверять, передали ли туда данные нужного типа и бросать исключение?
Согласен, компилятор F# генерит отличные компараторы. В своё время пару классах для задач на графах решил написать на F# в отдельной сборке, и не пожалел. Впрочем, там существует понятие структурной эквивалентности, которым в C# не пахнет, и которое не нужно было бы, если б записи изначально лучше поддерживались в C# или самой платформе.
Ответить на поставленный вопрос не так уж сложно. Всё определяется тем, является ли ваш тип объектом.
К объектам в используемой объектной среде всегда добавляется уникальный идентификатор. В C++ это указатель, в C#/Java это ссылка, в объектных БД это OID, и даже когда в реляционной БД вы используете суррогатный ключ — это тоже в каком-то смысле искуственный уникальный идентификатор. Собственно, необходимость в таком идентификаторе и есть ответ на вопрос, чем является тип с точки зрения семантики. Возьмём ваш Person: скорее всего у вас это будет тип-объект, т.к. даже если у разных Person совпадают все данные, то всегда есть вероятность, что это разные люди. Иными словами, вам нужен искуственный идентификатор, чтобы их различать. Если же в рамках вашей задачи вас не интересуют люди без российского паспорта, и вы условились различать всех по номеру паспорта, то вы вполне можете рассматривать Person как тип-значение.
А как именно, кстати, сравнивать? — для строк-имен — с учетом регистра или без, с учетом какой культуры, учитывать ли ведущие и ведомые пробельные символы, ...
Это не нужно рассматривать в рамках задачи сравнения Person. Если ФИО нужно сравнивать иначе, например без учёта регистра, нужно тогда для полей Name использовать не стандартный string, а некий NameString, который, к примеру, будет сравниваться регистронезависимо. При этом он всё равно будет оставаться типом-значением. Вопрос сравнения нельзя рассматривать независимо от типизации.
По возможности, строить архитектуру классов таким образом, чтобы эта задача не возникала.
Не могу согласиться с этим, также как не могу согласиться с тем, что в реляционной БД все таблицы должны иметь целочисленный суррогатный ID.
что если нам нужно в словарь поместить все возможные записи, и потом уже разбираться, где дубли с различающимися неключевыми полями и почему?
И здесь опять следует разобраться, как вы понимаете ваши «записи». Если они могут иметь дубли, но вы таки хотите их различать, вы автоматически наделяете их каким-то отличительным признаком, даже если вы пока не понимаете, каким конкретно. Допустим, вы считываете записи из файла, и их содержимое действительно совпадает. Но вы тем не менее считаете эти записи разными. Как вы их различаете? Ну допустим вы можете говорить о порядковом номере записи. В таком случае именно он является идентификатором и его нужно добавить в ваши дублирующиеся «записи», чтобы перестать считать их дублирующимися. Тогда их можно спокойно добавлять в один список. Либо, вы можете объявить эти «записи» объектами, и тогда такой идентификатор даст вам ваша языковая среда. В случае C# это будет ссылка на объект. Вы также сможете положить несколько ваших дублирующихся по содержимому «записей» в один список.
Позже, когда вы решите задачу с дублирующимися записями, вы скорее всего всё-таки захотите отказаться от номера записи в качестве идентификатора и будете использовать содержимое СНИЛС для сравнения — иначе зачем вообще этот СНИЛС, если он не идентифицирует?
Вообще это больная тема для многих разработчиков и проектов, и касается она почти всех архитектурных слоёв — от пользовательского интерфейса до базы данных.
Кому должны? В каком случае DTO вообще сравнивать надо?
Ну, к примеру, определить наличие изменений в ответе сервера, если нет более удобных механизмов вроде ETag. Хочется же один раз такое сравнение написать, и пользоваться им. Логично, что в этом случае DTO должен сравниваться как запись. У него нет своего identity, это лишь слепок состояния объекта.
Кому должны?
У типа всегда есть семантика. Я думаю вы сильно удивитесь, если некая языковая среда скажет что 5 != 5 только потому, что 5 и 5 это разные «объекты». У вас есть определённые ожидания от поведения числовых литералов. Аналогичные ожидания есть и в моём примере с DTO.
Я уже более 10 лет программирую на .NET и еще ни разу не написал в реальном проекте свою реализацию Equals.
Видимо вы не пользовались NHibernate :). Он Equals любит)
Любой случай, когда семантически сущность ведёт себя как значение (запись, record), но технически является объектом. За примером далеко ходить не надо, можно взять стандартный System.String.
Лично я считаю, что отличную идею разведения на два лагеря сущностей-значений/записей (которые тождественны, если тождественна их структура/содержимое) и сущностей-объектов (которые имеют identity и для которых объект тождественнен только самому себе) в дотнете не довели до ума. На struct-ы, которые как раз и должны использоваться для «записей», накладывается слишком много технических ограничений. В результате, выбирать между struct и class приходится чисто их технических соображений, и приходится брать class для реализации типов-записей. В результате чего и имеем типы со смешанным поведением, которые как бы объекты, передаются по ссылке, но сравниваются по значению. Из реальных примеров могу привести Data Transfer Object, которые не более чем записи и должны сравниваться по содержимому.
Во-первых, генерируемая коллекция может быть бесконечной. Во-вторых, функция с yield return это сопрограмма, и компилятор сгенерит её конечный автомат за вас.
Надо же, такая холиварная статья, а никто не вспомнил про фичу TypeScript 2. Возможно, пора набраться смелости и пойти на аналогичный шаг и в C# — добавить в компилятор режим, в котором будет использоваться более строгое подмножество языка. Не могу сказать, насколько это выполнимо в принципе, но было бы весьма заманчиво.
VB6, о, моя молодость. Как гляну на иконку окна, так сразу... (роняет скупую слезу)
https://www.asus.com/ru/Mobile/Phones/ZenFone/ZenFone_2_ZE551ML/
А вообще, это так не работает. Если архитектура призвана жрать много электричества, её будут ставить туда, где можно жрать много, и ничего за это не будет.
В телефонах ARM потому что её можно подключить к аккумулятору, и она всегда будет там лучше, чем x86.
Раз вы задаёте этот вопрос, значит вы используете подход "отдельная директория для public хедеров", как и в этой статье. Ну т.е. когда все публичные заголовочные файлы физически перемещаются в отдельную директорию.
В их папках обычно не создаётся CMakeLists просто потому, что с ними особо нечего делать, кроме как скопировать на этапе install (лично я советую делать это и на этапе сборки, особенно если есть генерируемые хедеры, и использовать их ТОЛЬКО из build-директории, а не из source-директории, но это вопрос отдельный). Раньше копировали как могли, потому что довольно давно существующее свойство таргета
PUBLIC_HEADER
не сохраняет структуру директорий для публичных хедеров, и потому им проще не пользоваться вовсе.НО! В версии 3.23 наконец приехал стандартный механизм, который реально работает для публичных хедеров - file sets. Единственный на данный момент поддерживаемый тип file set-а это как раз
HEADERS
. Для этого типа даже автоматически изменяетсяINCLUDE_DIRECTORIES
таргета. Ну и самое главное - приinstall(TARGETS)
сохраняется относительная структура файлов и директорий - как раз то, что обычно нужно для публичных хедеров.target_sources
предпочтительнее, просто он появился относительно недавно (по меркам CMake) и мало кто его ещё использует. https://crascit.com/2016/01/31/enhanced-source-file-handling-with-target_sources/Есть ещё холиварный вопрос - использовать ли директиву file(GLOB) или все файлы всегда перечислять в CMakeLists. Лично я пользуюсь глобом, но у него много противников.
Кстати, почему
include
, а неadd_subdirectory
? С инклудом у вас же не будет зеркалироваться структура дерева исходников в билд-дерево.Мне кажется это самый разумный и единственный работающий подход. Спасибо Conan-у за то, что именно такой путь они и выбрали, по сути стандартизировав сборку в разных конфигурациях. Все остальные велосипеды жутко усложняет работу.
Тот же GenerateExportHeader генерирует экспортный хедер, который вполне готов к использованию и в STATIC-варианте. В случае размещения только одной конфигурации в одной директории, никакие дополнительные ухищрения не нужны.
Кстати, поддержка мультиконфигурационности в самом CMake - тоже не сахар. Она сделана по сути только для нескольких генераторов, причём прежде всего для IDE вроде Visual Studio, и сильно усложняет код скриптов кучей одинаковых переменных, но для разных конфигураций. Мне кажется гораздо правильнее реализовать такое по append-принципу "несколько прогонов cmake-скрипта -> один солюшен/проектный файл", чтобы новые конфигурации просто "дописывались" в уже сгенерированный проект. Но имеем что имеем.
Спасибо за статью! Отсутствие best practices в документации самого CMake - это, пожалуй, его главная проблема сегодня. Даже синтаксис - и тот можно стерпеть.
Можно купить подписку на 365 в розничной продаже.
Спасибо за статью.
Мне кажется здесь нарушена логическая цепочка. Почему, на ваш взгляд, если у человека занижена самооценка, он не желает напрягаться? Разве не наоборот?
Почему спрашиваю: вы установили входной фильтр, а такое решение не может быть необдуманным. А я к сожалению не смог нарисовать портрет человека, который должен идти мимо ещё на этапе фильтрации соискателей в форме поиска.
После прочтения таких статей, ради самоуспокоения всегда напоминаю себе о том, как возросли наши требования как пользователей:
все разучились управлять информацией. Никто больше не ценит простые инструменты, вроде иерархической файловой системы в целях создания порядка. Тех, кто раскладывает музыку во FLAC-е по сотням папочек, считают душными дедами. Все пользуются поиском на стриминговых сервисах.
ладно, не все разучились. Кто-то не умел с самого начала, и вряд ли бы научился. Ведь мы пригласили пользоваться информационными технологиями своих родителей, своих друзей. Им нужны более интуитивные интерфейсы, анимации, подсказки.
теперь мы рассчитываем на информационные технологии ГОРАЗДО сильнее, чем ранее. Многие уже забыли, что такое BSOD и фарш на файловой системе просто при наборе текста в Word. Согласитесь, даже ненавистная Винда сегодня куда более стабильная, чем её древние предки 95/98/Me.
многие вещи, вроде уже упомянутого Юникода, воспринимаются как должное. А за это тоже нужно платить сложностью. Тот же протокол TLS, который по меркам 90-х - супернавороченная вещь уровня дорогущего банковского ПО. А сейчас мы боимся даже картинку скачать по HTTP, вдруг куки/токены угонят.
Ну т.е. если подумать, у софта было достаточно причин стать сложнее.
Конечно, ситуация с JS, вебом и этими вашими Электронами - это другое (c). Это просто одна немаленькая корпорация выиграла битву за программную платформу, и мы пожинаем плоды этой победы. Надеюсь WebAssembly когда-нибудь свергнет JS с должности платформы, "под которую лучше писать по-умолчанию, чтобы дотянуться до максимального числа пользователей". Мне кажется, это самое большое легаси во всём IT на данный момент.
Добавлю ещё про ключик Update Build Revision:
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\UBR
. Оттуда как раз можно получить второе число из OS Build, которое как раз и выдаётся командойver
. Это же число видно и в окне System > About в панели управления.Используем вот такую штуку года где-то с 2018-го:
применяется например вот так:
Качество кода выросло очень заметно.
Instra сегодня сделала рассылку о своём решении по поводу текущих событий. Выдержка по существу:
Это издёвка?
Во-первых, я думаю вы понимаете, что если у вас есть JSDoc и тесты, то статическая типизация у вас УЖЕ есть, только неявно и контроллируется она вручную. То, что вы умеете и можете это делать средставами, удобными именно вам, не значит что ваше решение объективно лучшее.
Во-вторых, ошибки на этапе компиляции — это, пожалуй, объективно полезно. Да, вы можете сказать, что тесты вы запускаете также в процессе сборки проекта и можно сказать что это эквивалентное средство. Но если я могу достичь аналогичного результата с помощью информации, которая обрабатывается И компиляторо И программистом одинаково (синтаксис строго-типизированного языка), зачем мне эту информацию разделять между документацией для разоработчика (JSDoc) и тестами?
В третьих, я думаю тесты — это замечательная вещь, которой можно и нужно покрыть всё, что НЕ УДАЛОСЬ покрыть более продвинутыми проверками, такими как типизация (и прочие виды проверок в compile-time). Иными словами, я считаю мы должны идти от тестов к более специализированным и строгим средствам верификации, а не наоборот.
Давать программисту сначала свободу в виде динамической типизации всех переменных и значений, а потом в большинстве случаев эту свободу «забирать», ограничивая тестами РЕАЛЬНЫЕ ветви исполнения кода и РЕАЛЬНЫЕ типы данных, которые код может обработать — это неэффективно. А в реальном коде именно так и будет происходить — не так уже часто нужен действительно «полностью динамический» тип (top-type или any).
И да, я до сих пор не понимаю как форсировать контракты в большой команде разработчиков на динамическом языке. В каждой функции проверять, передали ли туда данные нужного типа и бросать исключение?
К объектам в используемой объектной среде всегда добавляется уникальный идентификатор. В C++ это указатель, в C#/Java это ссылка, в объектных БД это OID, и даже когда в реляционной БД вы используете суррогатный ключ — это тоже в каком-то смысле искуственный уникальный идентификатор. Собственно, необходимость в таком идентификаторе и есть ответ на вопрос, чем является тип с точки зрения семантики. Возьмём ваш Person: скорее всего у вас это будет тип-объект, т.к. даже если у разных Person совпадают все данные, то всегда есть вероятность, что это разные люди. Иными словами, вам нужен искуственный идентификатор, чтобы их различать. Если же в рамках вашей задачи вас не интересуют люди без российского паспорта, и вы условились различать всех по номеру паспорта, то вы вполне можете рассматривать Person как тип-значение.
Это не нужно рассматривать в рамках задачи сравнения Person. Если ФИО нужно сравнивать иначе, например без учёта регистра, нужно тогда для полей Name использовать не стандартный string, а некий NameString, который, к примеру, будет сравниваться регистронезависимо. При этом он всё равно будет оставаться типом-значением. Вопрос сравнения нельзя рассматривать независимо от типизации.
Не могу согласиться с этим, также как не могу согласиться с тем, что в реляционной БД все таблицы должны иметь целочисленный суррогатный ID.
И здесь опять следует разобраться, как вы понимаете ваши «записи». Если они могут иметь дубли, но вы таки хотите их различать, вы автоматически наделяете их каким-то отличительным признаком, даже если вы пока не понимаете, каким конкретно. Допустим, вы считываете записи из файла, и их содержимое действительно совпадает. Но вы тем не менее считаете эти записи разными. Как вы их различаете? Ну допустим вы можете говорить о порядковом номере записи. В таком случае именно он является идентификатором и его нужно добавить в ваши дублирующиеся «записи», чтобы перестать считать их дублирующимися. Тогда их можно спокойно добавлять в один список. Либо, вы можете объявить эти «записи» объектами, и тогда такой идентификатор даст вам ваша языковая среда. В случае C# это будет ссылка на объект. Вы также сможете положить несколько ваших дублирующихся по содержимому «записей» в один список.
Позже, когда вы решите задачу с дублирующимися записями, вы скорее всего всё-таки захотите отказаться от номера записи в качестве идентификатора и будете использовать содержимое СНИЛС для сравнения — иначе зачем вообще этот СНИЛС, если он не идентифицирует?
Вообще это больная тема для многих разработчиков и проектов, и касается она почти всех архитектурных слоёв — от пользовательского интерфейса до базы данных.
Ну, к примеру, определить наличие изменений в ответе сервера, если нет более удобных механизмов вроде ETag. Хочется же один раз такое сравнение написать, и пользоваться им. Логично, что в этом случае DTO должен сравниваться как запись. У него нет своего identity, это лишь слепок состояния объекта.
У типа всегда есть семантика. Я думаю вы сильно удивитесь, если некая языковая среда скажет что 5 != 5 только потому, что 5 и 5 это разные «объекты». У вас есть определённые ожидания от поведения числовых литералов. Аналогичные ожидания есть и в моём примере с DTO.
Видимо вы не пользовались NHibernate :). Он Equals любит)
Лично я считаю, что отличную идею разведения на два лагеря сущностей-значений/записей (которые тождественны, если тождественна их структура/содержимое) и сущностей-объектов (которые имеют identity и для которых объект тождественнен только самому себе) в дотнете не довели до ума. На struct-ы, которые как раз и должны использоваться для «записей», накладывается слишком много технических ограничений. В результате, выбирать между struct и class приходится чисто их технических соображений, и приходится брать class для реализации типов-записей. В результате чего и имеем типы со смешанным поведением, которые как бы объекты, передаются по ссылке, но сравниваются по значению. Из реальных примеров могу привести Data Transfer Object, которые не более чем записи и должны сравниваться по содержимому.
Как говорил Хоар про null-ы у указателей — ошибка на миллиард.