Типовой дизайн: типы значений, конструктивность по умолчанию, необязательный «T» и его взаимосвязь?

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

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

Позже есть некоторые типы, для которых их построение по умолчанию не дает значения. Например, в стандартной библиотеке у нас есть std::function<Sig> и std::thread, например. Тем не менее, они конструируются по умолчанию, даже если они не содержат значения.

Позже у нас есть предложенный optional<T> в стандарте. Имеет смысл использовать его для базовых типов, так как для базовых типов все возможные присваивания представляют допустимое значение (кроме double и float NaN), но я не понимаю, как бы вы использовали его для thread или std::function<Sig> , так как эти типы не содержат «значение» при создании. Это КАК ЕСЛИ бы эти типы имели «необязательные», встроенные в тип напрямую.

Это имеет эти недостатки. Поскольку нет «естественной» конструкции по умолчанию (или значения), например, с int:

  • Теперь я должен засорить свой класс if (valid) во всем своем дизайне и сигнализировать об ошибке. ИЛИ
  • сделать его менее безопасным, если я не сделаю эту проверку. Предварительное условие -> назначить перед использованием, если построено по умолчанию.

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

Плюсы:

  • Легче повторно использовать в более общих контекстах, потому что мой тип будет легче моделировать SemiRegular или Regular, если я добавлю соответствующие операции.

Минусы:

  • Засорите весь класс операторами if или заключите контракт с пользователем, в котором использование класса более небезопасно.

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

  • Я не могу просто найти естественный способ создать «Песню по умолчанию».
  • Я должен засорить if (validsong) или сделать его небезопасным для использования.

Итак, мои вопросы:

  1. Как мне разработать тип, который не имеет «естественных (как по значению)» значений по умолчанию? Должен ли я предоставить конструктор по умолчанию или нет?

  2. В случае, если я предоставлю конструктор по умолчанию, как optional<T> впишется во всю эту головоломку? Я считаю, что создание типа, который не является «естественно» конструктивным по умолчанию, предоставляет конструктор по умолчанию, что делает optional<T> бесполезным в этом случае.

  3. Следует ли использовать optional<T> только для типов, область значений которых заполнена, то есть я не могу присвоить недопустимое значение его представлению, потому что все они содержат значение, например, int?

  4. Почему такие типы, как std::function<Sig>, изначально стали конструируемыми по умолчанию в стандарте? При построении он не содержит значения, поэтому я не понимаю, почему должен быть предоставлен конструктор по умолчанию. Вы всегда можете сделать: optional<function<void ()>>, например. Является ли это просто выбором дизайна, и оба варианта допустимы, или в данном случае есть один дизайн, заключающийся в выборе конструктивного элемента по умолчанию и не по умолчанию, который превосходит другой?


person Germán Diago    schedule 29.10.2014    source источник
comment
На самом деле меня очень интересовало бы мнение Степанова и Шона Пэрента по этим вопросам. Любой материал? P.S.: Элементы программирования не дочитал, может ответ есть.   -  person Germán Diago    schedule 29.10.2014


Ответы (1)


(Примечание: проблема с большим количеством вопросов в одном вопросе заключается в том, что некоторые его части могут дублироваться. Лучше всего задавать более мелкие вопросы и проверять каждый на наличие предыдущих сообщений. «Один вопрос на вопрос» — хорошая политика; проще думаю, иногда сказано, чем сделано.)

  1. Почему такие типы, как std::function, изначально стали конструируемыми по умолчанию? При построении он не содержит значения, поэтому я не понимаю, почему должен быть предоставлен конструктор по умолчанию. Вы всегда можете сделать: optional<function<void ()>>, например.

См. Почему экземпляры std::function имеют конструктор по умолчанию?

  1. Как мне разработать тип, который не имеет «естественных (как по значению)» значений по умолчанию? Должен ли я предоставить конструктор по умолчанию или нет?

Конструкторы по умолчанию для типов, которым трудно осмысленно определить себя без каких-либо данных, — это то, как многие классы реализуют нулевое значение. Является ли опциональным лучшим выбором? Обычно я так думаю, но я предполагаю, что вы знаете, что std::optional был отключен от С++14. Даже если бы это был идеальный ответ, он не может быть ответом для всех... это еще не суп.

Это всегда будет добавлять некоторые накладные расходы для отслеживания времени выполнения, если значение привязано или нет. Возможно, не так много много накладных расходов. Но когда вы используете язык, raison d'etre которого состоит в том, чтобы разрешить абстракцию, в то же время позволяя вам выстрелить себе в ногу настолько близко к металлу, насколько вы хотите... сокращение байта на значение в гигантском векторе может быть важным.

Таким образом, даже если семантика optional<T> и проверка во время компиляции были идеальными, вы все равно можете столкнуться со сценарием, в котором выгодно отказаться от него и позволить вашему типу кодировать свою собственную недействительность. Нужно подтолкнуть эти пиксели, или полигоны, или пакеты, или... пфаффтауны.

  1. В случае, если я предоставлю конструктор по умолчанию, как option впишется во всю эту головоломку? Я считаю, что создание типа, который не является «естественно» конструктивным по умолчанию, предоставляет конструктор по умолчанию, что делает необязательный бесполезным в этом случае.

  2. Должен ли необязательный просто использоваться для типов, область значений которых завершена, то есть я не могу присвоить недопустимое значение его представлению, потому что все они содержат значение (за исключением float и double NaN, я думаю).

В моем собственном случае я обнаружил, что хочу различать проверки во время компиляции между подпрограммами, которые могут обрабатывать нулевые указатели, и теми, которые не могут. Но внезапно optional<pointer> предложил эту ситуацию: либо необязательный параметр не привязан, либо привязан к нулевому указателю, либо привязан к ненулевому указателю. Проверка работоспособности во время компиляции казалась меньшей победой, которую она имела.

Так как насчет необязательных ссылок? Они противоречивы до такой степени, что в последний раз я слышал, что они являются одним из камней преткновения в наборе вещей, которые задержали std:: optional из С++ 14. Что немного раздражало после того, как я преобразовал необязательные указатели в необязательные ссылки. :-/

У меня была смутная идея написать книгу о "патологическом C++", где вы выбираете какую-то идею и начинаете доводить ее до логических выводов. optional<T> было одним ударом, который я получил и следовал по существу принципам, которые вы определили. Удалите возможность «недействительности» от кодирования в самом типе, и тогда вы внезапно можете заставить компилятор выполнять проверку типов на предмет того, готов ли данный бит кода ожидать null или нет.

(Сегодня я склонен подозревать, что если вы сильно зациклитесь на такого рода «патологическом C++», вы закончите заново изобретать Haskell. :-/ См. популярный Data.Maybe monad.)

person HostileFork says dont trust SE    schedule 29.10.2014
comment
Я думаю, что эти вопросы настолько тесно связаны между собой, что если я их разделю, то на самом деле потеряю суть. Вы должны рассматривать это как все, что связано — конструкция по умолчанию, необязательная, регулярность и компромиссы. - person Germán Diago; 29.10.2014
comment
@GermánDiago Конечно ... мы видим, что все программирование связано, поэтому все в StackOverflow указывает на один большой вопрос. Вот почему задача вопросов и ответов состоит в том, чтобы разбить этот один большой вопрос на независимо повторно используемые фрагменты институциональных знаний. Очень похоже на вызов в программном обеспечении. Суть архитектуры заключается в сокрытии информации, не необходимой для выполнения поставленной задачи, и поэтому как-то уместно, что сама природа архитектуры такова, что она никогда не представляет нам себя целиком, а лишь в одной или двух гранях. раз. :-) - person HostileFork says dont trust SE; 29.10.2014
comment
Спасибо за совет. У меня твердое мнение, что в данном случае это имеет смысл, как сейчас. - person Germán Diago; 29.10.2014
comment
@GermánDiago Примите также во внимание мое твердое мнение: если вы обнаружите, что такого рода проблемы вызывают у вас страсть, а не отвлекают ... вы можете сесть с Изучите Haskell и Typeclassopedia и осознайте, что C++ не точит этот конкретный топор, разве что в качестве хобби по прихоти. Если вы слишком сильно нажимаете на C++, вам нужно либо по-другому думать о своих приоритетах, либо просто меньше думать в целом, либо переключать языки. :-) - person HostileFork says dont trust SE; 29.10.2014
comment
Мне нравится Haskell, но я считаю неприличным кодировать только функциональными способами. Хотя, признаю, функционал мощный. С другой стороны, экосистема/сообщество для c/c++ настолько огромно, что почти непобедимо с точки зрения библиотеки :) - person Germán Diago; 29.10.2014
comment
Кстати, я взял немного Haskell раньше. Концептуально это самый мощный язык, который я когда-либо видел. Но мне также нужны ИСПОЛЬЗУЕМЫЕ непрерывные векторы ввода-вывода и некоторая изменчивость для нормального кодирования. Я знаю о монадах. Они такие мощные... и... абстрактные. - person Germán Diago; 29.10.2014