Здравствуйте, jazzer, Вы писали:
J>Здравствуйте, Quasi, Вы писали:
Q>>void f(boost::system::error_code& ec = boost::system::throws);
Q>>Нельзя невольно проигнорировать ошибку, исключает необходимость дублирования интерфейса J>И исключает возможность привязки по указателю на функцию
Здравствуйте, Николай Ивченков, Вы писали:
НИ>Юрий Жмеренецкий:
ЮЖ>>Throws: ЮЖ>>boost::thread_resource_error if an error occurs. [/q]
ЮЖ>>При такой формулировке (общей для всех lock types), с отсутствующими предусловиями — возбуждение исключения единственно верный вариант.
НИ>Какой же он верный, когда boost::lock_error и boost::thread_resource_error — это два несвязанных наследованием исключения?
Вопрос Андрея был в том, что использовать — исключение или ассерт. Согласно формулировкам приведенным в документции ассерты использовать нельзя, но на использование исключения ни он, ни ты, как я понял, не согласны. Как в этом случае можно рассуждать о конкретном типе исключения? Это уже следующая 'итерация' вопроса — реализация выбранного способа (и я не утверждаю что это хороший способ — просто документация не оставляет другого выбора).
Re[7]: api design: return code or exception - formal criteri
Здравствуйте, Quasi, Вы писали:
Q>Здравствуйте, jazzer, Вы писали:
J>>Здравствуйте, Quasi, Вы писали:
Q>>>void f(boost::system::error_code& ec = boost::system::throws);
Q>>>Нельзя невольно проигнорировать ошибку, исключает необходимость дублирования интерфейса J>>И исключает возможность привязки по указателю на функцию
Q>
Q>В чем собственно проблема? Или я неправильно понял?
1) нельзя к указателю на функцию привязать
2) нужно все время писать boost::ref(boost::system::throws)
Когда просто вызваешь функцию, разницы никакой, хоть они одна, хоть две, а вот связать такую функцию с указателем уже нельзя.
И даже функцию-переходник без параметров нельзя сделать — будет ругаться на неоднозначность.
Так что для пользователей твоей библиотеки никаких плюсов нету, им гораздо удобнее иметь две функции, котороые обе можно связывать с указателями.
Q>>В чем собственно проблема? Или я неправильно понял? J>1) нельзя к указателю на функцию привязать
А интересно, а откуда такая необходимость, с учетом того, что сигнатура функции содержит С++ типы
Здравствуйте, Quasi, Вы писали:
Q>>>В чем собственно проблема? Или я неправильно понял? J>>1) нельзя к указателю на функцию привязать Q>А интересно, а откуда такая необходимость, с учетом того, что сигнатура функции содержит С++ типы
а какая разница, какие типы содержит сигнатура?
J>>2) нужно все время писать boost::ref(boost::system::throws) Q>Тут тоже интересный момент, в ином случае, когда их две : Q>
вопрос был собственно в том, зачем вообще в C++ проекте использовать указатели на функции С++ библиотеки, использование функторов, например, boost(почти std)::function дает намного больше гибкости, а в этом случае биндить перегруженные функции менее удобно. Единственное, что приходит в голову, это оптимизация
Re[7]: api design: return code or exception - formal criteri
НИ>>Юрий Жмеренецкий:
ЮЖ>>>Throws: ЮЖ>>>boost::thread_resource_error if an error occurs. [/q]
ЮЖ>>>При такой формулировке (общей для всех lock types), с отсутствующими предусловиями — возбуждение исключения единственно верный вариант.
НИ>>Какой же он верный, когда boost::lock_error и boost::thread_resource_error — это два несвязанных наследованием исключения?
ЮЖ>Вопрос Андрея был в том, что использовать — исключение или ассерт. Согласно формулировкам приведенным в документции ассерты использовать нельзя, но на использование исключения ни он, ни ты, как я понял, не согласны. Как в этом случае можно рассуждать о конкретном типе исключения? Это уже следующая 'итерация' вопроса — реализация выбранного способа (и я не утверждаю что это хороший способ — просто документация не оставляет другого выбора).
Я не против использования исключений. Мое мнение — тут _другая_ мотивация, а не то, что вы пытаетесь рассказывать
Locable, например, кидает исключения и в случае lock_guard, который в свою очередь, никаких своих исключений кидать не может. Но. Его состояние при этом остается валидным, в отличие от unique_lock. Все остальное — вторичные следствия, а никак не первичные.
AS>>Не согласен. Приведенная формулировка концепта никоим образом не влияет на то, что именно должен делать lock холдер в случае нарушения _своего_ предусловия.
ЮЖ>Очень даже влияет — объект, моделирующий этот концепт, не должен усиливать предусловия (и ослаблять постусловия) для функции lock. unique_lock не имеет "своего" предусловия для ф-ии lock, т.к:
ЮЖ>
Specializations of boost::unique_lock model the TimedLockable concept if the supplied Lockable type itself models TimedLockable concept (e.g. boost::unique_lock<boost::timed_mutex>), or the Lockable concept otherwise.
И что? unique_lock никоим образом не ослабляет концепт Locable ни при ассерте, ни при генерации исключения. Просто при генерации исключения lockable'ом он остается в невалидном состоянии. А насчет дополнительного предусловия — как раз метод lock его имеет. У unique_lock есть внутреннее состояние, смотрите реализацию.
Здравствуйте, Andrew S, Вы писали:
AS>>>Не согласен. Приведенная формулировка концепта никоим образом не влияет на то, что именно должен делать lock холдер в случае нарушения _своего_ предусловия.
ЮЖ>>Очень даже влияет — объект, моделирующий этот концепт, не должен усиливать предусловия (и ослаблять постусловия) для функции lock. unique_lock не имеет "своего" предусловия для ф-ии lock, т.к:
ЮЖ>>
Specializations of boost::unique_lock model the TimedLockable concept if the supplied Lockable type itself models TimedLockable concept (e.g. boost::unique_lock<boost::timed_mutex>), or the Lockable concept otherwise.
AS>И что? unique_lock никоим образом не ослабляет концепт Locable
Что такое "ослабление концепта"?
AS> ни при ассерте, ни при генерации исключения.
В случае использования Вашей формулировки — наличия "_своего_ предусловия" возникает очевидное нарушение LSP, аналогичное нарушению здесь:
struct A
{
/// \pre a > 0virtual void f(int a) = 0;
};
struct B : A
{
/// \pre a > 100 void f(int a);
};
Концептуально, 'A' эквивалентно Lockable, а B — unique_lock. Эта эквивалентность следует из документации (которая сама по себе может содержать ошибки, быть неактуальной и т.п.)
Имея на руках любой объект, моделирующий Lockable, для вызова метода lock мы должны выполнить только предусловия этого метода (отсутствие сформулированных предусловий означает что метод можно вызвать в любое время, в любом состоянии объекта, и т.д. — это общепринятая практика). Проблема в том, что unique_lock, согласно документации, должен выполнять требования, налагаемые на все методы Lockable. И документация на unique_lock::lock отсутствует (среди других функций) именно по этой причине.
Как только 'принадлежность' к Locable будет снята, то все ваши выкладки (и мои в т.ч., в первом сообщении я упоминал об этом) становятся правдоподобными.
AS>Просто при генерации исключения lockable'ом он остается в невалидном состоянии.
Валидное/невалидное — здесь это субъективное определение, образующей здесь служат пред и постусловия.
AS>А насчет дополнительного предусловия — как раз метод lock его имеет.
Определение контракта по реализации — это не очень хороший метод, т.к. во первых — мы берем за эталон контракт 'такой как есть', вместо того, который 'должен быть', во вторых — это совершенно лишняя работа, в третьих — реализация может быть недоступна.
AS>У unique_lock есть внутреннее состояние, смотрите реализацию.
Наличие/отсутствие состояния — это следствие, вызванное необходимостью выполнения контрактов.
Re: api design: return code or exception - formal criteria
Здравствуйте, Andrew S, Вы писали:
AS>Если есть ссылки критерии, best practice или что-нибудь подобное — будет супер.
Вот тебе пример. Есть библотека X, разработчики которой решили сделать парные функции, одни с поддержкой исключений, другие с поддержкой кода возврата. Появились пользователи библиотеки (программисты) которые перестали использовать функции с кодом возврата, а только фунции бросающие исключения. После появились программисты библиотеки, которые поняли, что каждый раз включать галку в студии останавливаться на исключении в отладчике не катит и вставили ассерт перед самим throw. После появились ещё более хитрожопые пользователи библиотеки, которые решили всё-таки ловить исключения через try-catch из-за чего стали появляться бесполезные ассерты перед каждым throw, т.е. ассерт перед throw не сигнализирующий ошибку, т.к. исключение впоследствии ловится и обрабатывается.
К чему это всё скажите вы? А к тому что иногда поддержка исключений библиотекой приводит к образованию некоторого пользовательского болота, организованного недобросовестными программистами использующими библиотеку, из которого вам впоследствии будет трудно выбраться.
Иногда лучше вообще не пользоваться исключениями и "заставлять" пользователя обрабатывать код возврата.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Re[9]: api design: return code or exception - formal criteri
AS>>И что? unique_lock никоим образом не ослабляет концепт Locable
ЮЖ>Что такое "ослабление концепта"?
Увеличивает множество возможных состояний, например, при помощи адаптера.
AS>> ни при ассерте, ни при генерации исключения.
ЮЖ>В случае использования Вашей формулировки — наличия "_своего_ предусловия" возникает очевидное нарушение LSP, аналогичное нарушению здесь:
ЮЖ>
struct A
ЮЖ>{
ЮЖ> /// \pre a > 0
ЮЖ> virtual void f(int a) = 0;
ЮЖ>};
ЮЖ>struct B : A
ЮЖ>{
ЮЖ> /// \pre a > 100
ЮЖ> void f(int a);
ЮЖ>};
ЮЖ>Концептуально, 'A' эквивалентно Lockable, а B — unique_lock. Эта эквивалентность следует из документации (которая сама по себе может содержать ошибки, быть неактуальной и т.п.)
Не эквивалентно. unque_lock и locakble — абсолютно разные сущности, они всего-лишь сотрудничают, поэтому (теоретически) unique_lock может реализовать _любой_ контракт на свой lock. Как — это ее проблемы, с точки зрения пользователя это совершенно другая сущность, никак не locable. B и A же связаны гораздо сильнее, а именно отношение наследования и полиморфного поведения.
ЮЖ>Имея на руках любой объект, моделирующий Lockable, для вызова метода lock мы должны выполнить только предусловия этого метода (отсутствие сформулированных предусловий означает что метод можно вызвать в любое время, в любом состоянии объекта, и т.д. — это общепринятая практика). Проблема в том, что unique_lock, согласно документации, должен выполнять требования, налагаемые на все методы Lockable. И документация на unique_lock::lock отсутствует (среди других функций) именно по этой причине.
lock у unique_lock при корректном поведении пользователя в любом случае контракту lockable будет удовлетворять — ведь нигде не сказано, например, что нельзя два раза подряд вызвать lock у lockable. Сказано лишь, вызову lock должен соответствовать unlock (иначе, очевидно, не было бы возможно рекурсивных lockable — recursive_mutex). Ввиду наличия контракта на свое состояние (что unique_lock может вызывать lock только один раз ввиду семантики самого unique_lock, а не lockable), у unique_lock после дополнительного вызова состояние оказывается неверным — он не может обеспечить контракт lockable.
Здравствуйте, Andrew S, Вы писали:
ЮЖ>>В случае использования Вашей формулировки — наличия "_своего_ предусловия" возникает очевидное нарушение LSP, аналогичное нарушению здесь:
ЮЖ>>
struct A
ЮЖ>>{
ЮЖ>> /// \pre a > 0
ЮЖ>> virtual void f(int a) = 0;
ЮЖ>>};
ЮЖ>>struct B : A
ЮЖ>>{
ЮЖ>> /// \pre a > 100
ЮЖ>> void f(int a);
ЮЖ>>};
ЮЖ>>Концептуально, 'A' эквивалентно Lockable, а B — unique_lock. Эта эквивалентность следует из документации (которая сама по себе может содержать ошибки, быть неактуальной и т.п.)
AS>Не эквивалентно.
Собственно вот он — камень преткновения.
AS>unque_lock и locakble — абсолютно разные сущности, они всего-лишь сотрудничают AS>поэтому (теоретически) unique_lock может реализовать _любой_ контракт на свой lock. Как — это ее проблемы, с точки зрения пользователя это совершенно другая сущность, никак не locable. B и A же связаны гораздо сильнее, а именно отношение наследования и полиморфного поведения.
В оригинальной формулировке LSP дихотомия subtype/supertype не имеет никакого отношения к классическому наследованию. Наследование — один из возможных способов выражения таких отношений.
С этой точки зрения — утверждение "unque_lock и locakble — абсолютно разные сущности" неверно.
Между ними существуют отношения subtype/supertype, несмотря на то что это декларируется только документацией. Соответственно и поведение unque_lock (как lockable объекта) должно быть таким, как того требует спецификация lockable concept. Некоторые девиации все же допустимы — реализация может изменять контракты, но только в жестко ограниченных рамках — нельзя усиливать предусловия и ослаблять постусловия, Так же к provable property можно отнести и генерацию исключений методами — см. замечание Николая про boost::thread_resource_error.
Все это нужно для того, что бы в контексте, в котором выполняются предусловия для Lockable:lock можно было свободно использовать любые методы, в качестве параметров которых используется сущность реализующая Lockable concept, например:
Preconditions:
The value_type of ForwardIterator must implement the Lockable concept
ЮЖ>>Имея на руках любой объект, моделирующий Lockable, для вызова метода lock мы должны выполнить только предусловия этого метода (отсутствие сформулированных предусловий означает что метод можно вызвать в любое время, в любом состоянии объекта, и т.д. — это общепринятая практика). Проблема в том, что unique_lock, согласно документации, должен выполнять требования, налагаемые на все методы Lockable. И документация на unique_lock::lock отсутствует (среди других функций) именно по этой причине.
AS>lock у unique_lock при корректном поведении пользователя в любом случае контракту lockable будет удовлетворять
Не в любом. Как только вы вводите "контракт на свое состояние" _и_ этот контракт не вписывается (см. выше про ограничения) в контракт для supertype — возникает нарушение LSP. В этом случае нельзя использовать unique_lock там где требуется любая сущность, реализующая Lockable, т.к. она обладает уже другим контрактом.
Очевидный выход — либо устранить какую-либо связь между unique_lock и Lockable, либо изменить контракты Lockable. В той ситуации, которую мы имеем — добавление любого предусловия (отличного от true) для unique_lock::lock делает реализацию unique_lock некорректной (для существующей спецификации).
Re[2]: api design: return code or exception - formal criteri
Здравствуйте, Vain, Вы писали:
V>Здравствуйте, Andrew S, Вы писали:
AS>>Если есть ссылки критерии, best practice или что-нибудь подобное — будет супер.
[snip]
V>К чему это всё скажите вы? А к тому что иногда поддержка исключений библиотекой приводит к образованию некоторого пользовательского болота, организованного недобросовестными программистами использующими библиотеку, из которого вам впоследствии будет трудно выбраться. V>Иногда лучше вообще не пользоваться исключениями и "заставлять" пользователя обрабатывать код возврата.
А мне показалось, что лучше не ставить ассерты перед throw и недобросовестеыми тут выглядят как раз программисты библиотеки, сделавшие это.
The last good thing written in C was Franz Schubert's Symphony No. 9.
Re[2]: api design: return code or exception - formal criteri
Здравствуйте, Vain, Вы писали:
V>Иногда лучше вообще не пользоваться исключениями и "заставлять" пользователя обрабатывать код возврата.
IMHO, пользователей библиотеки, которые "развели болото" не удастся заставить верно обрабатывать коды возврата...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[7]: api design: return code or exception - formal criteri
Юрий Жмеренецкий:
ЮЖ>Вопрос Андрея был в том, что использовать — исключение или ассерт. Согласно формулировкам приведенным в документции ассерты использовать нельзя, но на использование исключения ни он, ни ты, как я понял, не согласны. Как в этом случае можно рассуждать о конкретном типе исключения? Это уже следующая 'итерация' вопроса — реализация выбранного способа (и я не утверждаю что это хороший способ — просто документация не оставляет другого выбора).
У меня есть подозрения, что тут всё же подразумеваются некие "слабые" концепции, выражающие общие требования к классу без предусловий выполнения этих требований. Т.е. класс удовлетворяет требованиям "слабой" концепции, если существует специфичный для него набор предусловий, при выполнении которых он поддерживает все операции в соответствии с указанной семантикой. В черновике нового стандарта, насколько я вижу, описывать блокировки через концепции вообще не планировали. Там таких непоняток уже не возникает. Например, в отношении нерекурсивного мьютекса std::mutex чётко сказано, что попытка повторного вызова lock в пределах одного потока влечёт undefined behavior — фактически здесь предусловием вызова lock является отсутствие блокировки мьютекса текущим потоком. Вероятно, то же самое следует подразумевать и в отношении boost::mutex, однако концепция Lockable таких ограничений не содержит.
Re[8]: api design: return code or exception - formal criteri
[...]
НИ>У меня есть подозрения, что тут всё же подразумеваются некие "слабые" концепции, выражающие общие требования к классу без предусловий выполнения этих требований. Т.е. класс удовлетворяет требованиям "слабой" концепции, если существует специфичный для него набор предусловий, при выполнении которых он поддерживает все операции в соответствии с указанной семантикой.
Для каких целей нужны такие концепции? Да, они описывают поведение, но только в некотором классе состояний [объекта], причем не специфицируют метод определения принадлежности к этому классу. Как c их использованием можно сделать, например, следующее:
Даже если ввести метод — предикат (P), который отвечает на вопрос 'удовлетворяет ли объект класса X концепции Y в настоящий момент?' то появляются дополнительные проблемы: например — любой вызов метода, относящегося к концепции Y может (если этому не препятствовать) привести к тому, что значение P станет равным false, т.е. фактически вызов метода концепции выводит объект из состояния, в котором он удовлетворял этой концепции.
Поведение unique_lock как раз попадает под этот сценарий (схематично) — в 'unlocked' состоянии: частичное соответствие Lockable concept (возможность вызова 'lock'), после вызова lock — опять частичное соответствие (возможность вызова 'unlock'). Плюс, в любом состоянии статическое соответствие (набор функций).
Т.е. фактически получается конечный автомат из концепций, + функции для перехода между ними, + (опционально) предикаты для проверок/наблюдения.
После спецификации всего этого можно описывать конкретные сущности, определяя структуру такого конечного автомата (для поддержки рекурсии КА недостаточно, но это уже детали), а так же пред и постусловия для переходов между состояниями ('микро' концепциями). Но в таком случае уже нельзя просто так сказать 'объект класса X моделирует/реализует Lockable concept'.
В той же ситуации, которую мы имеем на текущий момент, повторюсь, введение любого предусловия для метода unique_lock::lock приводит к нарушению LSP.
Re[11]: api design: return code or exception - formal criter
ЮЖ>>>Концептуально, 'A' эквивалентно Lockable, а B — unique_lock. Эта эквивалентность следует из документации (которая сама по себе может содержать ошибки, быть неактуальной и т.п.)
AS>>Не эквивалентно. ЮЖ>Собственно вот он — камень преткновения.
AS>>unque_lock и locakble — абсолютно разные сущности, они всего-лишь сотрудничают AS>>поэтому (теоретически) unique_lock может реализовать _любой_ контракт на свой lock. Как — это ее проблемы, с точки зрения пользователя это совершенно другая сущность, никак не locable. B и A же связаны гораздо сильнее, а именно отношение наследования и полиморфного поведения.
ЮЖ>В оригинальной формулировке LSP дихотомия subtype/supertype не имеет никакого отношения к классическому наследованию. Наследование — один из возможных способов выражения таких отношений.
Кто сказал, что unique_lock и Locable связаны отношением подстановки аналогично приведенному вами А и Б? Для пользователя эти сущности имеют разный контракт. Хотя контракт unique_lock и включает в себя _часть_ контракта Lockable, он имеет дополнительные ограничения и самое главное — различные протоколы. Этак можно сказать, что любые объекты, которые имеют конструктор и деструктор, являются подтипами.
Итого. Никакого отношения к связи между unique_lock и lockable принцип LSP и наследование, как его реализация, не имеет.
Например, вот:
Liskov's notion of a behavioral subtype defines a notion of substitutability for mutable objects; that is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).
Для unique_lock, очевидно, это не выполняется. У этих сущностей разные контракты, и пример этой разницы хотя бы для рекурсивного Locable я уже привел. Но, разницы не сколько и не столько в этом — а в том, что Loсkable — это по сути концепция мьютекса, которым управляют _извне_, а unique_lock — концепция автообъекта, который в большинстве случае _сам_ управляет подчиненным объектом. И хотя сам unique_lock может моделировать _часть_ концепции Lockable, очевидно, у него есть и своя часть контракта, которая определяется только им самим. И именно она не выполняется в указанном мной случае.
ЮЖ>С этой точки зрения — утверждение "unque_lock и locakble — абсолютно разные сущности" неверно.
Верно. Потому что отношение между locable и unique_lock в общем случае никакого отношения к LSP не имеет. По крайней мере, то, что есть исходя из текущей реализации в бусте.
Вот определения из документации:
Specializations of boost::unique_lock model the TimedLockable concept if the supplied Lockable type itself models TimedLockable concept (e.g. boost::unique_lock<boost::timed_mutex>), or the Lockable concept otherwise
и
boost::recursive_mutex implements the Lockable concept
Очевидно, в данном случае unique_lock сужает контракт boost::recursive_mutex, и тогда либо boost::recursive_mutex все же имеет контракт отличный от Lockable, либо все же unique_lock реализует только часть этого контракта. Т.о. документация противоречит сама себе, и на практике Lockable не является подтипом unique_lock, и наоборот — unique_lock также не является подтипом Lockable для случая, когда в качестве Lockable выступает рекурсивный мьютекс.
Но, все это совершенно не влияет на суть рассматриваемого вопроса. На самом деле, совершенно не важно, каким образом связаны контракты сущностей — самое главное, что они различны. Нарушается часть контракта _самого_ unique_lock, которая обеспечивается его состоянием. И после этого он уже не может выполнить контракт Lockable.
Юрий Жмеренецкий:
НИ>>У меня есть подозрения, что тут всё же подразумеваются некие "слабые" концепции, выражающие общие требования к классу без предусловий выполнения этих требований. Т.е. класс удовлетворяет требованиям "слабой" концепции, если существует специфичный для него набор предусловий, при выполнении которых он поддерживает все операции в соответствии с указанной семантикой.
ЮЖ>Для каких целей нужны такие концепции?
Для тех же, что и обычные концепции.
ЮЖ>Да, они описывают поведение, но только в некотором классе состояний [объекта], причем не специфицируют метод определения принадлежности к этому классу. Как c их использованием можно сделать, например, следующее:
ЮЖ>
Данная функция, помимо принадлежности arg к Lockable, должна описывать характер использования arg (впрочем, тогда в ряде случаев можно вообще обойтись без упоминания о Lockable ). Исходя из этого описания пользователь функции должен сделать выводы о том, соблюдаются предусловия для его случая или нет. Фактически "слабая" концепция — это просто незавершённая концепция. Автор класса, формулируя конкретные предусловия, создаёт из неё завершённую концепцию (но без явного имени).
ЮЖ>В той же ситуации, которую мы имеем на текущий момент, повторюсь, введение любого предусловия для метода unique_lock::lock приводит к нарушению LSP.
Только если считать Lockable завершённой концепцией. Ладно, что ты скажешь по поводу следующих двух вопросов?
1) Соответствует ли бустовой концепции Lockable класс std::mutex из N2914 — 30.4.1.1?
2) Может ли этот std::mutex быть использован для инстанцирования boost::unique_lock?
Re[12]: api design: return code or exception - formal criter
Здравствуйте, Andrew S, Вы писали:
ЮЖ>>В оригинальной формулировке LSP дихотомия subtype/supertype не имеет никакого отношения к классическому наследованию. Наследование — один из возможных способов выражения таких отношений.
AS>Кто сказал, что unique_lock и Locable связаны отношением подстановки аналогично приведенному вами А и Б?
Cуществование non member function lock. Приведенная Вами цитата из документации. Хотя хватит даже любой пользовательской шаблонной функции, принимающей Lockable объект.
AS>Для пользователя эти сущности имеют разный контракт.
Определение контрактов для методов Lockable я вижу. Отдельного определения контрактов функций unique_lock::lock, try_lock, unlock — нет. Вместо этого я вижу ссылку на Lockable, и соответственно на контракты соответствующих методов. Вывод очевиден — контракты для всех трех функций у этих двух сущностей одинаковы. Этот вывод получен из документации.
AS>Этак можно сказать, что любые объекты, которые имеют конструктор и деструктор, являются подтипами.
Можно. Другой пример: POD объекты, которые можно копировать с помощью memcpy — substitutability в чистом виде. Или std::advance, принимающая итераторы. Или non member function lock.
AS>Никакого отношения к связи между unique_lock и lockable принцип LSP и наследование, как его реализация, не имеет.
Наследование не является "реализацией" LSP. С помощью наследования можно моделировать subtyping relation, но это далеко не единственная возможность.
AS>Например, вот:
AS>
AS>Liskov's notion of a behavioral subtype defines a notion of substitutability for mutable objects; that is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).
AS>Для unique_lock, очевидно, это не выполняется.
Цитата судя по всему из википедии...
А что именно не выполняется — вся фраза целиком? Это и есть нарушение LSP. Desirable properties — это те самые выводы, полученные из документации.
Если же не выполняется какая-то отдельная часть, уточните.
ЮЖ>>С этой точки зрения — утверждение "unque_lock и locakble — абсолютно разные сущности" неверно.
AS>Верно. Потому что отношение между locable и unique_lock в общем случае никакого отношения к LSP не имеет.
Ммм... Не имеет по определению или не имеет, потому что при использовании возникает нарушение и поэтому такое использование не разрешается?
AS>По крайней мере, то, что есть исходя из текущей реализации в бусте.
Издалека:
Вот эта функция корректна:
unsigned int x(unsigned int a, unsigned int b)
{
return a + b;
}
или нет?
Правильный ответ — может быть. Точный ответ зависит от спецификации — если результатом должна быть сумма (как она формулируется для unsigned int) двух чисел — то корректна, иначе — нет. Ваш вывод о наличии дополнительных состояний, выполнения 'частей контракта' получен путем анализа исходного кода/исходя из каких-либо допущений/etc. и для функции 'x' (выше) будет звучать как 'эта функция складывает числа'. Да, она их складывает, но предположим что в документации написано — она их должна перемножать. Кто прав — Вы или документация? Независимо от ответа, функция некорректна — она делает не то, что написано в документации (desirable/provable/etc. properties).
Возвращаясь к нашим концепциям: посмотрите документацию — из нее следует очевидный вывод (см. выше) — контракты для трех функций одинаковы в обоих случаях. Возникает противоречие — мы не можем эксплуатировать задокументированную эквивалентность контрактов в силу реализации. И происходит это именно из-за LSP, т.к. концепция Lockable помимо сигнатур функций специфицирует поведение как набор пред и постусловий. Необходимость наличия своего состояния у unique_lock, к которой Вы аппелируйте, вызвана ничем иным как следствием необходимости выполнения своих, специфических предусловий. А они, в свою очередь, никак не вписываются в Lockable без нарушения LSP.
Re[13]: api design: return code or exception - formal criter
ЮЖ>>>В оригинальной формулировке LSP дихотомия subtype/supertype не имеет никакого отношения к классическому наследованию. Наследование — один из возможных способов выражения таких отношений.
AS>>Кто сказал, что unique_lock и Locable связаны отношением подстановки аналогично приведенному вами А и Б?
ЮЖ>Cуществование non member function lock. Приведенная Вами цитата из документации. Хотя хватит даже любой пользовательской шаблонной функции, принимающей Lockable объект.
AS>>Для пользователя эти сущности имеют разный контракт.
ЮЖ>Определение контрактов для методов Lockable я вижу. Отдельного определения контрактов функций unique_lock::lock, try_lock, unlock — нет. Вместо этого я вижу ссылку на Lockable, и соответственно на контракты соответствующих методов. Вывод очевиден — контракты для всех трех функций у этих двух сущностей одинаковы. Этот вывод получен из документации.
Я вам привел пример, когда контракт Lockable не может быть выполнен. Поскольку документация в этом случае unclear — все остальное говорит реализация.
ЮЖ>Возвращаясь к нашим концепциям: посмотрите документацию — из нее следует очевидный вывод (см. выше) — контракты для трех функций одинаковы в обоих случаях. Возникает противоречие — мы не можем эксплуатировать задокументированную эквивалентность контрактов в силу реализации. И происходит это именно из-за LSP, т.к. концепция Lockable помимо сигнатур функций специфицирует поведение как набор пред и постусловий. Необходимость наличия своего состояния у unique_lock, к которой Вы аппелируйте, вызвана ничем иным как следствием необходимости выполнения своих, специфических предусловий. А они, в свою очередь, никак не вписываются в Lockable без нарушения LSP.
Все что, сказано в документации, что unique_lock моделирует концепт lockable, если инстанцирована им. Но. Реализация и назначение unique_lock показывают, что это не так. Иначе хватило бы просто ассерта. Итого — в топку либо документацию либо реализацию.
Тем не менее. Представим, что реализация делает именно то, что и должна. Изначальный вопрос звучал так — почему тут исключение, а не assert? И то, и то может быть использовано как сигнализатор нарушения контракта. И исключение тут именно потому, что состояние объекта unique_lock после нарушения контракта является не валидным.
В общем, предлагаю закругляться, никаких новых аргументов уже не появляется.