
![]() |
Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
|
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.238.180.174] |
![]() |
|
Страницы: (3) [1] 2 3 все ( Перейти к последнему сообщению ) |
![]() |
Сообщ.
#1
,
|
|
Пролистывая старые темы, наткнулся на пример.
оригинал на C++ от Qraizer ![]() ![]() // Шаблон, использующий статический полиморфизм template <typename MT_Policy> class Foo: public MT_Policy { /* ... */ public: /* ... */ void DoSomething() { this->lock(); /* do something */ this->unclock(); } }; // Реализация для однопоточного приложения class NoSync { public: void lock() {} void unlock(){} }; // Реализация через критические секции class CT_Sync { CRITICAL_SECTION ct; CT_Sync(const CT_Sync&); void operator=(const CT_Sync&); public: CT_Sync() { InitializeCriticalSection(&ct); } ~CT_Sync() { DeleteCriticalSection (&ct); } void lock() { EnterCriticalSection(&ct); } void unlock(){ LeaveCriticalSection(&ct); } }; // Реализация через мьютексы class Mutex_Sync { HANDLE hMutex; Mutex_Sync(const Mutex_Sync&); void operator=(const Mutex_Sync&); public: Mutex_Sync(): hMutex(CreateMutex(NULL, FALSE, NULL)) { if (hMutex == NULL) throw std::runtime_error("Can\'t create a mutex"); } ~Mutex_Sync() { CloseHandle(hMutex); } void lock() { WaitForSingleObject(hMutex, INFINITE); } void unlock(){ ReleaseMutex(hMutex); } }; // Использование Foo<CT_Sync> privateFoo; Foo<Mutex_Sync> sharedFoo; Foo<NoSync> upToF_ckFoo; Далее IL_Agent привёл вариант на C#, заметив, что унаследоваться от параметра дженерика нельзя. вариант на C# от IL_Agent ![]() ![]() class Foo<Policy> where Policy : ISync { Policy sync = Activator.CreateInstance<Policy>(); public void DoSomething() { sync.Lock(); //... sync.Unlock(); } } interface ISync { void Lock(); void Unlock(); } class NoSync:ISync { public void Lock() { } public void Unlock() { } } class SomeSync : ISync { //... public SomeSync(){/*...*/ } public void Lock() {/*...*/ } public void Unlock(){/*...*/ } } //... var f1 = new Foo<NoSync>(); var f2 = new Foo<SomeSync>(); Код упрощённый — в C# нет RAII, поэтому нужна ещё финализация (например, для политики с критической секцией). Также, D_KEY заметил, что в C++ lock/unlock могут быть статическими функциями. В оригинальном примере они вызываются через this->, в Java можно вызывать статические функции через this. В C++ тоже? Или имелось в виду, что в шаблоне нужно вызывать их без this->, тогда можно будет использовать статические? Или нужен второй шаблон Foo для "статического" вида политик? D_KEY, уточни, пожалуйста. Решил попробовать реализовать аналог на Ocaml и вот что получилось: ![]() ![]() module type MT_policy = sig type t class handler : object val handle : t method finalize : unit end val lock : t -> unit val unlock : t -> unit end module Foo (Policy : MT_policy) : sig class t : object method do_something : unit method finalize : unit end end = struct class t = object inherit Policy.handler method do_something = Policy.lock handle ; print_endline "do something" ; Policy.unlock handle end end (* ---------------------------------------------------------------- *) (* ---------------- external library ---------------- *) module CRITICAL_SECTION : sig type t val define : unit -> t val initialize : t -> unit val delete : t -> unit val enter : t -> unit val leave : t -> unit end = struct type t = int ref let define () = ref 0 let log action sec = Printf.printf "%s SEC %d\n" action !sec let initialize sec = sec := 42 ; log "INIT" sec let delete sec = log "DELETE" sec ; sec := 0 let enter sec = log "ENTER" sec let leave sec = log "LEAVE" sec end module Mutex : sig type t val create : unit -> t val wait_for : t -> unit val release : t -> unit end = struct type t = Mx let create () = Mx let wait_for Mx = print_endline "Waiting for mutex..." let release Mx = print_endline "Release mutex" end (* ---------------------------------------------------------------- *) module No_sync = struct type t = unit class handler = object val handle = () method finalize = () end let lock () = print_endline "no lock" let unlock () = print_endline "no unlock" end module CT_sync = struct type t = CRITICAL_SECTION.t class handler = object val handle = CRITICAL_SECTION.define () method finalize = CRITICAL_SECTION.delete handle initializer CRITICAL_SECTION.initialize handle end let lock = CRITICAL_SECTION.enter let unlock = CRITICAL_SECTION.leave end module MX_sync = struct type t = Mutex.t class handler = object val handle = Mutex.create () method finalize = () end let lock = Mutex.wait_for let unlock = Mutex.release end (* ---------------------------------------------------------------- *) let new_foo (policy : (module MT_policy)) = let module F = Foo (val policy) in new F.t type 'a finalizable = < finalize : unit ; .. > as 'a let using : 'a finalizable -> ('a -> unit) -> unit = fun res f -> f res ; res #finalize let _ = using (new_foo (module CT_sync)) @@ fun private_foo -> using (new_foo (module MX_sync)) @@ fun shared_foo -> using (new_foo (module No_sync)) @@ fun up_to_f_ck_foo -> print_endline "---- doing something..." ; private_foo # do_something ; shared_foo # do_something ; up_to_f_ck_foo # do_something ; print_endline "---- finalizing..." Результат запуска: ![]() ![]() INIT SEC 42 ---- doing something... ENTER SEC 42 do something LEAVE SEC 42 Waiting for mutex... do something Release mutex no lock do something no unlock ---- finalizing... DELETE SEC 42 — https://www.jdoodle.com/ia/Acd В Ocaml тоже нет RAII, поэтому нужна явная финализация. В остальном: функции lock/unlock полностью абстрактные, могут быть «статическими», могут — не быть, например, в конкретной политике можно реализовать тип t как класс с методами lock/unlock, которые будут вызываться из функций. Класс Policy.handler можно наследовать, можно — не наследовать. Предлагайте свои варианты на других языках (статически типизированных, конечно). C# там уже был; Java, наверное, не будет от него отличаться слишком (хотя, вдруг, у кого есть альтернативные идеи). Может, тут Swift'ингеры есть? Или Rust'оманы? Или D_KEY вспомнит Scala? =) Также было бы интересно увидеть ещё какие-нибудь подобные примеры использования шаблонов. (Наверняка, они были в тех многочисленных старых холиварах, но копаться там — занятие такое себе. А тут можно было бы собрать несколько интересных примеров в одной теме) |
Сообщ.
#2
,
|
|
|
Цитата korvin @ В оригинальном примере они вызываются через this->, в Java можно вызывать статические функции через this. В C++ тоже? Да, тоже можно. Добавлено Цитата korvin @ Предлагайте свои варианты на других языках (статически типизированных, конечно). Тут вся фишка в С++ в том, что политиками ты статически как бы "собираешь" свой тип, но при этом ты ничем не жертвуешь в динамкие. Т.е. как будто ты сам писал все эти классы руками делал. Т.е. абстракция остается исключительно в коде, а не в итоговой программе. Не думаю, что в других языках такое есть. Может на неделе посмотрю, есть ли что-то такое в расте. |
![]() |
Сообщ.
#3
,
|
|
Откровенно говоря, в моём примере тоже нет финализации, но её легко построить:
![]() ![]() template <typename MT_Policy> class Guard { MT_Policy& synk; public: explicit Guard(MT_Policy& obj): synk(obj) { synk.lock(); } ~Guard() { synk.unlock(); } }; Тогда использование будет чуточку проще, но и гарантированно безопасно: ![]() ![]() template <typename MT_Policy> class Foo: public MT_Policy { /* ... */ public: /* ... */ void DoSomething() { Guard<MT_Policy> guardian(*this); /* do something */ } }; В общем случае можно защитить не только функцию целиком, но и произвольный кусок кода, достаточно заключить его в синтетический блок {}: ![]() ![]() void DoSomething() { /* do something */ { Guard<MT_Policy> guardian(*this); /* do something more */ } /* continue something doing*/ } |
Сообщ.
#4
,
|
|
|
Qraizer, зачем там свой Guard? Достаточно удовлетворять BasicLockable и можно юзать std::lock_guard.
|
![]() |
Сообщ.
#5
,
|
|
Тогда и CT_Sync с Mutex_Sync не нужны, ведь есть std::mutex и std::recursive_mutex. Вопрос в той теме стоял несколько по-другому: продемонстрировать возможности шаблонов в части устранения ненужного динамического оверхеда интерфейсов в случае статики. Я не стал менять коней, ибо не для всякой ситуации в std есть готовые патерны.
Добавлено P.S. Ну и чтобы совсем хорошо, следует избавиться от наследования в пользу объекта-агрегата. Наследование там не требуется, а агрегат можно сделать mutable и т.с. позволить работать в константных методах. |
Сообщ.
#6
,
|
|
|
Цитата Qraizer @ Тогда и CT_Sync с Mutex_Sync не нужны, ведь есть std::mutex и std::recursive_mutex. Ну мы же о стратегиях говорим. Можем написать разные классы, в том числе и для отсутствия блокировок. При этом std::lock_guard будет применим ![]() |
![]() |
Сообщ.
#7
,
|
|
Блин. Ты не понял. Ну давай за стратегию безопасности кастов целых тогда. Вот я хочу чекать переполнения или не хочу. Или за стратегию транспорта для сетевого протокола. Элементарно TCP vs UDP. У тебя есть что-то в std для этого?
Ещё раз. В общем случае нужно рассчитывать, что стратегии придётся писать фуллстэк. На всех уровнях, распространяя их сверху донизу иерархии или наоборот. И шаблоны на это способны. Тут да, можно применить std::, я просто не стал менять коней на полпути. |
![]() |
Сообщ.
#8
,
|
|
Цитата Qraizer @ Откровенно говоря, в моём примере тоже нет финализации Хм, а я думал деструктора достаточно: ![]() ![]() public: CT_Sync() { InitializeCriticalSection(&ct); } ~CT_Sync() { DeleteCriticalSection (&ct); } |
![]() |
Сообщ.
#9
,
|
|
Это если мы за собственно объект. А как быть с блокировкой на нём? Нужно обеспечить корректное освобождение даже при исключениях, что и делает RAII. В исходном коде RAII существовала для объекта синхронизации, но не блокировки на нём.
Хотя, это зависит от того, что ты понимаешь под финализацией, конечно. |
![]() |
Сообщ.
#10
,
|
|
Я тем временем сделал альтернативную Ocaml-версию, без объектов, чисто на функторах, и заодно немного по-другому сделал финализацию:
![]() ![]() module type MT_policy = sig val lock : unit -> unit val unlock : unit -> unit end module Foo (Policy : MT_policy) = struct let do_something () = Policy.lock () ; print_endline "Doing something..." ; Policy.unlock () end (* -------- EXT LIB -------- *) module CRITICAL_SECTION : sig type t val value : unit -> t val initialize : t -> unit val enter : t -> unit val leave : t -> unit val delete : t -> unit end = struct type t = int ref let value () = ref 0 let initialize sec = let sid = Random.int 100 in Printf.printf "---- INIT SEC %d\n" sid ; sec := sid let enter sec = Printf.printf "ENTER SEC %d\n" !sec let leave sec = Printf.printf "LEAVE SEC %d\n" !sec let delete sec = Printf.printf "---- DEL SEC %d\n" !sec ; sec := 0 end module Mutex : sig type t val construct : unit -> t val acquire : t -> unit val release : t -> unit val destruct : t -> unit end = struct type t = int ref let construct () = let mid = 100 + Random.int 100 in Printf.printf "---- CONSTR MUX %d\n" mid ; ref mid let acquire mux = Printf.printf "ACQ MUX %d\n" !mux let release mux = Printf.printf "REL MUX %d\n" !mux let destruct mux = Printf.printf "---- DESTR MUX %d\n" !mux ; mux := 0 end (* -------------------------------- *) module No_sync : MT_policy = struct let lock () = () let unlock () = () end module type REGION = sig val finalize : ('a -> unit) -> 'a -> unit end module CT_sync (R : REGION) : MT_policy = struct let ct = CRITICAL_SECTION.value () let _ = CRITICAL_SECTION.initialize ct let _ = R.finalize CRITICAL_SECTION.delete ct let lock () = CRITICAL_SECTION.enter ct let unlock () = CRITICAL_SECTION.leave ct end module MX_sync (R : REGION) : MT_policy = struct let mx = Mutex.construct () let _ = R.finalize Mutex.destruct mx let lock () = Mutex.acquire mx let unlock () = Mutex.release mx end (* -------------------------------- *) module Region () : sig val finalize : ('a -> unit) -> 'a -> unit val exit : unit -> unit end = struct let finalizers = ref [] let finalize f v = finalizers := (fun () -> f v) :: !finalizers let rec exit () = match !finalizers with | [] -> () | f::fs -> begin f () ; finalizers := fs ; exit () end end let _ = Random.self_init () ; let module REGION = Region () in let module F1 = Foo (CT_sync (REGION)) in let module F2 = Foo (MX_sync (REGION)) in let module F3 = Foo (No_sync) in let module F4 = Foo (CT_sync (REGION)) in let module F5 = Foo (MX_sync (REGION)) in F1.do_something () ; F2.do_something () ; F3.do_something () ; F4.do_something () ; F5.do_something () ; REGION.exit () => ![]() ![]() ---- INIT SEC 35 ---- CONSTR MUX 181 ---- INIT SEC 0 ---- CONSTR MUX 168 ENTER SEC 35 Doing something... LEAVE SEC 35 ACQ MUX 181 Doing something... REL MUX 181 Doing something... ENTER SEC 0 Doing something... LEAVE SEC 0 ACQ MUX 168 Doing something... REL MUX 168 ---- DESTR MUX 168 ---- DEL SEC 0 ---- DESTR MUX 181 ---- DEL SEC 35 — https://godbolt.org/z/K4b6xGG15 |
![]() |
Сообщ.
#11
,
|
|
Хм. Формально ничто не запрещает на функторах переписать... D_KEY, как думаешь, стоит ли?
|
![]() |
Сообщ.
#12
,
|
|
Цитата Qraizer @ но не блокировки на нём. А, понял. Я про lock/unlock в doSomething и не думал, как-то второстепенным уже казалось )) |
![]() |
Сообщ.
#13
,
|
|
В целом да, это второстепенное. Но с точки зрения отказоустойчивости кода – ни разу не второстепенное. Надёжность моего кода не должна зависеть от надёжности чьего-то ещё. Без RAII можно заморочиться чеком всевозможных нюансов между lock() и unlock() внутри DoSomething(), и если заморочился и не набажил, то возьми с полки пирожок. Кто как, я вот предпочитаю пирожок в руках сразу, а не тянуться за ним до полки.
|
![]() |
Сообщ.
#14
,
|
|
В Ocaml можно вот так сделать, но это только в теле функции, аналог try-with-resources в Java и прочих:
![]() ![]() type 'a resource = { acquire : unit -> 'a ; release : 'a -> unit } let ( let& ) : 'a resource -> ('a -> 'b) -> 'b = fun r f -> let h = r.acquire () in Fun.protect ~finally:(fun () -> r.release h) begin fun () -> f h end module type MT_policy = sig val lock : unit -> unit val unlock : unit -> unit end module Foo (Policy : MT_policy) = struct let lock () = { acquire = Policy.lock ; release = Policy.unlock } let do_something () = let& _ = lock () in print_endline "Doing something..." end |
![]() |
Сообщ.
#15
,
|
|
Цитата Qraizer @ Формально ничто не запрещает на функторах переписать... В Ocaml под функторами немного другое подразумевается. Но я не отговариваю ) |