
![]() |
Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
|
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[216.73.216.75] |
![]() |
|
![]() |
Сообщ.
#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 под функторами немного другое подразумевается. Но я не отговариваю ) |
Сообщ.
#16
,
|
|
|
Цитата Qraizer @ Хм. Формально ничто не запрещает на функторах переписать... D_KEY, как думаешь, стоит ли? А зачем? ![]() Добавлено Цитата korvin @ В Ocaml можно вот так сделать, но это только в теле функции, аналог try-with-resources в Java и прочих Да, с плюсами разница как раз в том, что только в функции это работает. В С++ же это так же будет работать и для полей и автоматически вызываться при разрушении объекта. |
Сообщ.
#17
,
|
|
|
Цитата korvin @ void operator=(const CT_Sync&); Цитата korvin @ void operator=(const Mutex_Sync&); А что, так можно перегружать? Обычно же пишут что-то в виде: ![]() ![]() Type& operator=(const Type& t); |
![]() |
Сообщ.
#18
,
|
|
Раст.
![]() ![]() trait Policy { fn new() -> Self; fn lock(&mut self); fn unlock(&mut self); } struct LockGuard<'a, P: Policy> { p: &'a mut P, } impl<'a, P: Policy> LockGuard<'a, P> { fn new(p: &'a mut P) -> Self { p.lock(); Self { p } } } impl<'a, P: Policy> Drop for LockGuard<'a, P> { fn drop(&mut self) { self.p.unlock(); } } struct Foo<P: Policy> { p: P, } impl<P: Policy> Foo<P> { fn new() -> Foo<P> { Foo { p: Policy::new() } } fn do_smth(&mut self) { let lock = LockGuard::new(&mut self.p); // Do something println!("Hello!"); } } struct NoLock {} impl Policy for NoLock { fn new() -> Self { Self {} } fn lock(&mut self) {} fn unlock(&mut self) {} } Добавлено Из удобного - спева написал версию с ручным lock-unlock, потом добавил LockGuard, заменил вызов lock на создание объекта LockGuard и компилятор сразу выдал ошибку - дескать, unlock руками вызывать не можешь, т.к. объект отдали. BC всё-таки удобная вещь ![]() |
![]() |
Сообщ.
#19
,
|
|
Цитата D_KEY @ Да, с плюсами разница как раз в том, что только в функции это работает. В С++ же это так же будет работать и для полей и автоматически вызываться при разрушении объекта. Спасибо, Кэп )) |
![]() |
Сообщ.
#20
,
|
|
Можно так:
![]() ![]() module Region : sig type region val ( #: ) : region -> ('a -> unit) -> 'a -> unit val ( let& ) : (region -> 'a) -> ('a -> 'b) -> 'b end = struct type finalizer = unit -> unit type region = finalizer list ref let make () = ref [] let ( #: ) r f v = r := (fun () -> f v) :: !r let rec exit r = match !r with | [] -> () | f::fs -> begin f () ; r := fs ; exit r end let ( let& ) : (region -> 'a) -> ('a -> 'b) -> 'b = fun constr proc -> let region = make () in Fun.protect ~finally:(fun () -> exit region) begin fun () -> let res = constr region in proc res end end open Region module File : sig type t val open_rw : string -> region -> t val read_line : t -> string option val write_line : t -> string -> unit end = struct type t = { path : string ; mutable data : string list } let rec open_rw path region = Printf.printf "++++ OPEN %s\n" path ; let f = { path ; data = ["foo";"bar";"gee";"qux"] } in region #: close f ; f and close f = f.data <- [] ; Printf.printf "---- CLOSE %s\n" f.path let read_line f = match f.data with | [] -> begin Printf.printf "EOF\n" ; None end | line::rest -> begin f.data <- rest ; Printf.printf "READ %s FROM %s\n" line f.path ; Some line end let write_line f line = Printf.printf "WRITE %s TO %s\n" line f.path ; f.data <- List.append f.data [line] end module SQL_DB : sig type t val connect : string -> region -> t val select : t -> string list val insert : t -> string -> unit end = struct type t = { table : string ; mutable rows : string list } let rec connect table region = Printf.printf "++++ CONNECT TO %s\n" table ; let c = { table ; rows = ["foo";"bar"] } in region #: disconnect c ; c and disconnect conn = conn.rows <- [] ; Printf.printf "---- DISCONNECT FROM %s\n" conn.table let select conn = Printf.printf "SELECT * FROM %s\n" conn.table ; conn.rows let insert conn value = Printf.printf "INSERT INTO %s VALUE \"%s\"\n" conn.table value ; conn.rows <- value :: conn.rows end module Transfer : sig type t val make : db:string -> file:string -> region -> t val upload : t -> unit val download : t -> unit end = struct type t = { db : SQL_DB.t ; fd : File.t } let make ~db:conn_str ~file:path region = let db = SQL_DB.connect conn_str region in let fd = File.open_rw path region in { db : SQL_DB.t ; fd : File.t } let upload { db ; fd } = let rec loop () = match File.read_line fd with | None -> () | Some line -> begin SQL_DB.insert db line ; loop () end in loop () let download { db ; fd } = let lines = SQL_DB.select db in List.iter (File.write_line fd) lines end let _ = ( let& users = Transfer.make ~db:"users" ~file:"/tmp/users" in Transfer.upload users ; Transfer.download users ); print_endline "--------------------------------" ; ( let& posts = Transfer.make ~db:"posts" ~file:"/tmp/posts" in Transfer.download posts ; Transfer.upload posts ); print_endline "--------------------------------" ; ( let& users = Transfer.make ~db:"users" ~file:"/tmp/users" in let& posts = Transfer.make ~db:"posts" ~file:"/tmp/posts" in Transfer.download users ; Transfer.download posts ) — https://godbolt.org/z/eb8e44av5 ![]() ![]() ++++ CONNECT TO users ++++ OPEN /tmp/users READ foo FROM /tmp/users INSERT INTO users VALUE "foo" READ bar FROM /tmp/users INSERT INTO users VALUE "bar" READ gee FROM /tmp/users INSERT INTO users VALUE "gee" READ qux FROM /tmp/users INSERT INTO users VALUE "qux" EOF SELECT * FROM users WRITE qux TO /tmp/users WRITE gee TO /tmp/users WRITE bar TO /tmp/users WRITE foo TO /tmp/users WRITE foo TO /tmp/users WRITE bar TO /tmp/users ---- CLOSE /tmp/users ---- DISCONNECT FROM users -------------------------------- ++++ CONNECT TO posts ++++ OPEN /tmp/posts SELECT * FROM posts WRITE foo TO /tmp/posts WRITE bar TO /tmp/posts READ foo FROM /tmp/posts INSERT INTO posts VALUE "foo" READ bar FROM /tmp/posts INSERT INTO posts VALUE "bar" READ gee FROM /tmp/posts INSERT INTO posts VALUE "gee" READ qux FROM /tmp/posts INSERT INTO posts VALUE "qux" READ foo FROM /tmp/posts INSERT INTO posts VALUE "foo" READ bar FROM /tmp/posts INSERT INTO posts VALUE "bar" EOF ---- CLOSE /tmp/posts ---- DISCONNECT FROM posts -------------------------------- ++++ CONNECT TO users ++++ OPEN /tmp/users ++++ CONNECT TO posts ++++ OPEN /tmp/posts SELECT * FROM users WRITE foo TO /tmp/users WRITE bar TO /tmp/users SELECT * FROM posts WRITE foo TO /tmp/posts WRITE bar TO /tmp/posts ---- CLOSE /tmp/posts ---- DISCONNECT FROM posts ---- CLOSE /tmp/users ---- DISCONNECT FROM users |
![]() |
Сообщ.
#21
,
|
|
Цитата OpenGL @ ![]() ![]() struct Foo<P: Policy> { p: P, } В оригинальном примере в Foo не было поля p =/ С полем-то каждый может ![]() |
![]() |
Сообщ.
#22
,
|
|
Цитата korvin @ В оригинальном примере в Foo не было поля p =/ Это да. Но с другой стороны - чем оно мешает? На sizeof класса это не влияет, так что аналог empty base optimization (или как оно там в плюсах зовётся) тут применяется |
![]() |
Сообщ.
#23
,
|
|
Цитата Majestio @ Цитата korvin @ void operator=(const CT_Sync&); Цитата korvin @ void operator=(const Mutex_Sync&); А что, так можно перегружать? Обычно же пишут что-то в виде: ![]() ![]() Type& operator=(const Type& t); Это не обычная перегрузка. Это перегрузка с целью запрета генерировать эти спец.методы компилятором. Цель в том, чтобы запретить копирования и присваивания. Если их просто никак не объявить, компилятор способен будет сгенерировать их сам, если же они объявлены, то генерировать он уже ничего не будет. Но т.к. я их не определил, то при попытке скопировать или присвоить будет ошибка линковки. Т.е. цель запрета на эти операции достигнута, пусть и несколько странным способом. Т.к. я не планирую их использовать, то и прототип может быть любым, Стандарт не запрещает возвращать что ни попадя. Конечно, для обычной перегрузки так писать не стоит, кроме очень специфичных случаев. В новых Стандартах для такого запрета есть более правильные методы, но на момент написания того кода их ещё не было, или поддерживающие новые фишки Стандарта компиляторы мне не были доступны. |
Сообщ.
#24
,
|
|
|
Цитата Qraizer @ Это не обычная перегрузка. Это перегрузка с целью запрета генерировать эти спец.методы компилятором. Цель в том, чтобы запретить копирования и присваивания. Если их просто никак не объявить, компилятор способен будет сгенерировать их сам, если же они объявлены, то генерировать он уже ничего не будет. Но т.к. я их не определил, то при попытке скопировать или присвоить будет ошибка линковки. Т.е. цель запрета на эти операции достигнута, путь и несколько странным способом. Т.к. я не планирую их использовать, то и прототип может быть любым, Стандарт не запрещает возвращать что ни попадя. Конечно, для обычной перегрузки так писать не стоит, кроме очень специфичных случаев. В новых Стандартах для такого запрета есть более правильные методы, но на момент написания того кода их ещё не было, или поддерживающие новые фишки Стандарта компиляторы мне не были доступны. ![]() Что-то типа X& operator=(const X&) = delete; ? |
![]() |
Сообщ.
#25
,
|
|
Угу.
![]() |
![]() |
Сообщ.
#26
,
|
|
Цитата D_KEY @ Тут вся фишка в С++ в том, что политиками ты статически как бы "собираешь" свой тип, но при этом ты ничем не жертвуешь в динамкие. Т.е. как будто ты сам писал все эти классы руками делал. Т.е. абстракция остается исключительно в коде, а не в итоговой программе. Не думаю, что в других языках такое есть. В C# варианте, если IPolicy будут реализовывать структуры, а не классы, как в примере, то в ран тайм тоже не будет накладных расходов. |
Сообщ.
#27
,
|
|
|
Привет)
Справедливости ради, пример на C# был написан так, чтобы внешне быть похожим на плюсовый. На самом деле практической ценности в нём нет, так писать не стоит, дженерики тут не нужны. Достаточно передать в конструктор класса Foo нужный экземпляр ISync. И да, что-то "собрать" при компиляции можно с помощью кодогенерации (Roslyn, annotation processing, KSP и т.д), но тут это из пушки по воробьям) |
![]() |
Сообщ.
#28
,
|
|
Цитата IL_Agent @ Достаточно передать в конструктор класса Foo нужный экземпляр ISync. Вот как раз в этом случае у тебя будут потери на косвенный вызов метода через интерфейсную ссылку. Если это не парит, то и прекрасно. А во в случаях когда каждая микросекунда важна - дженерики - наше всё (в .NET) |
Сообщ.
#29
,
|
|
|
Цитата jack128 @ Вот как раз в этом случае у тебя будут потери на косвенный вызов метода через интерфейсную ссылку. Хочешь сказать, что вызов метода конкретного класса быстрее, чем вызов того же метода, но через интерфейс? Есть пруфы? |
![]() |
Сообщ.
#30
,
|
|
Для вызова метода интерфейса ж нужно сначала извлечь адрес метода из VMT и только потом сделать call, но тут разница микроскопическая. А вот что реально важно, так это inline. Если у нас в коде есть только интерфейс, то компилятор(JIT в случае .NET) не в курсе, что именно за метод должен вызваться и честно делает call <адрес метода> , вот если у нас есть структура мы вызваем myStruct.MyMethod(args) то jit сможет заинлайнить MyMethod со всеми вытекающими.
В .NET классический пример всего этого - это generic math в .NET 6 и ниже. Только после .NET7 таких примеров хрен нагуглишь :-D . А самому писать лень, сорри. |
![]() |
Сообщ.
#31
,
|
|
![]() ![]() #include <chrono> #include <iostream> #include <thread> #include <utility> /* динамически полиморфная иерархия */ struct DynaPolyBase // интерфейс { virtual void doIt() const = 0; }; // реализации struct DynaPolyDerived1 : DynaPolyBase { virtual void doIt() const {}; }; struct DynaPolyDerived2 : DynaPolyBase { virtual void doIt() const {}; }; struct DynaPolyDerived3 : DynaPolyDerived1 { virtual void doIt() const {}; }; /* статически полиморфная иерархия */ // реализации struct StatPolyDerived1 { void doIt() const {}; }; struct StatPolyDerived2 { void doIt() const {}; }; struct StatPolyDerived3 { void doIt() const {}; }; template <typename Class> requires ( requires { std::declval<Class>().doIt(); } ) // интерфейс struct StatPolyBase : Class { }; /* простой тестер */ template <typename C, auto Count = 1000u * 1000 * 1000> void doIt(const C& c) { for (decltype(Count) i = 0; i < Count; ++i) c.doIt(); } int main() { auto start = std::chrono::high_resolution_clock::now(); StatPolyBase<StatPolyDerived2> item1; DynaPolyDerived2 item2; doIt(item1); // чек статики auto diff = std::chrono::high_resolution_clock::now() - start; std::cout << diff << std::endl; start = std::chrono::high_resolution_clock::now(); doIt(item2); // чек динамики diff = std::chrono::high_resolution_clock::now() - start; std::cout << diff << std::endl; } ![]() ![]() 200ns 610564600ns |
Сообщ.
#32
,
|
|
|
Qraizer, что-то у тя какая-то жуть
![]() Я попробовал твой пример прогнать в https://www.onlinegdb.com, выбрал там стандарт С++20, чет он заругался на твой код в отдельных местах. Ну не суть, я подредактировал. Посмотри, я ничего в логике не поломал? ![]() ![]() #include <chrono> #include <iostream> #include <thread> #include <utility> /* динамически полиморфная иерархия */ struct DynaPolyBase // интерфейс { virtual void doIt() const = 0; }; // реализации struct DynaPolyDerived1 : DynaPolyBase { void doIt() const override {}; }; struct DynaPolyDerived2 : DynaPolyBase { void doIt() const override {}; }; struct DynaPolyDerived3 : DynaPolyDerived1 { void doIt() const override {}; }; /* статически полиморфная иерархия */ // реализации struct StatPolyDerived1 { void doIt() const {}; }; struct StatPolyDerived2 { void doIt() const {}; }; struct StatPolyDerived3 { void doIt() const {}; }; template <typename Class> // интерфейс struct StatPolyBase : Class { }; /* простой тестер */ template <typename C, auto Count = 1000u * 1000 * 1000> void doIt(const C& c) { for (auto i = 0; i < Count; ++i) c.doIt(); } int main() { StatPolyBase<StatPolyDerived2> item1; DynaPolyDerived2 item2; auto start = std::chrono::high_resolution_clock::now(); doIt(item1); // чек статики auto diff = std::chrono::high_resolution_clock::now() - start; std::cout << diff.count() << std::endl; start = std::chrono::high_resolution_clock::now(); doIt(item2); // чек динамики diff = std::chrono::high_resolution_clock::now() - start; std::cout << diff.count() << std::endl; } И вот какие я выводы получил после трех запусков: ![]() ![]() 2908372641 2742338534 ![]() ![]() 1867473111 2083640450 ![]() ![]() 1968410219 2046030004 А в твоем примере вообще какая-то лютая разница ![]() |
![]() |
Сообщ.
#33
,
|
|
Цитата Majestio @ В каких? Концепты не понял? С++20 в полной мере могут не все поддерживать. Та ну и хрен с ними, статика и на утиных работать будет, ей явно объявленные интерфейсы необязательны.Я попробовал твой пример прогнать в https://www.onlinegdb.com, выбрал там стандарт С++20, чет он заругался на твой код в отдельных местах. Ну не суть, я подредактировал. Но ты прав, я сутрировал конечно. Намерено, причём. Включи оптимизацию и познай дзен inline, как говорится. ![]() Твой пример ближе к истине, но тут такое дело... в общем, предсказатель переходов у современных процессоров всё равно не даст нормально потестить разницу в производительности между статически и динамически связанными вызовами. Надо как-то рандомизировать адресаты, чтобы у предсказателя побольше промахов было, иначе даже косвенные вызовы будут связаны статически, но в кэше процессора. И если для динамики это раз плюнуть, то вот для статики... Но это полбеды. Главная беда – это непонятно, а вообще нужно ли этот самый предсказатель нейтрализовывать. Как бы, синтетика синтетикой, но не до такой же степени, когда код сознательно пессимизируется. Добавлено С горем пополам у меня вышло ![]() ![]() 543405900ns 578759500ns Добавлено P.S. Ну не совсем, всё-таки. Код теперь выглядит примерно вот так: ![]() ![]() template <typename C> auto doIt(const C& c) { auto start = std::chrono::high_resolution_clock::now(); c.doIt(); return std::chrono::high_resolution_clock::now() - start; } /* ... */ StatPolyBase<StatPolyDerived1> itemS1; StatPolyBase<StatPolyDerived2> itemS2; StatPolyBase<StatPolyDerived3> itemS3; DynaPolyDerived1 *itemD1 = new DynaPolyDerived1; DynaPolyDerived2 *itemD2 = new DynaPolyDerived2; DynaPolyDerived3 *itemD3 = new DynaPolyDerived3; std::array<DynaPolyBase*, 3> items = { itemD1, itemD2, itemD3 }; std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> distrib(0, 2); decltype(doIt(itemS1)) time = decltype(time)::zero(); // статика for (int i = 0; i < 10000000; ++i) switch (distrib(gen)) { case 0: time += doIt(itemS1); break; case 1: time += doIt(itemS2); break; case 2: time += doIt(itemS3); break; } /* ... */ // динамика for (int i = 0; i < 10000000; ++i) time += doIt(*items[distrib(gen)]); delete itemD1; delete itemD2; delete itemD3; Добавлено Если же подойти к этому ну оооочень аккуратно, то: ![]() ![]() 547698900ns 833802200ns ![]() ![]() 700369200ns 1015749800ns ![]() ![]() 664954600ns 962311400ns Но надо понимать, что если у вас нет аппаратной защиты от всяких там Spectre и вместо стоят программные патчи, то эта синтетика будет показывать попугаев вместо наносекунд. |
Сообщ.
#34
,
|
|
|
Ну да, такое ближе к истине. А то я уже распереживался за динамику, чуть чяем не облился
![]() |