digitalmars.D.learn - RAII pointers
- Russel Winder via Digitalmars-d-learn (31/31) May 29 2017 C++ allows one to create types that are pointer types but wrap a
- Nicholas Wilson (2/6) May 29 2017 std.stdio.File does basically the same thing with C's FILE*
- Moritz Maxeiner (24/43) May 29 2017 Yes, I generally use structs with `@disable this(this)` and
- Russel Winder via Digitalmars-d-learn (32/81) Jun 03 2017 Thanks to Moritz and Stanislav for their examples, most useful. There
- Moritz Maxeiner (19/22) Jun 03 2017 Well, the differences I spot are:
- ag0aep6g (3/8) Jun 03 2017 `.init` is the corner case. `.init` is always there, even with `@disable...
- Moritz Maxeiner (8/17) Jun 03 2017 Of course, but AFAIK you'd need to explicitly assign it to an
- ag0aep6g (21/26) Jun 03 2017 I'd say `.init` can easily happen accidentally. Especially when
- Stanislav Blinov (8/35) Jun 03 2017 Yep, that's exactly why I added the null check in the example. If
- Moritz Maxeiner (6/18) Jun 03 2017 Calling std.algorithm.move is explicit programmer intent, I
- Stanislav Blinov (4/9) Jun 03 2017 Yes, it's explicit. Destructor call is still implicit though, and
- Moritz Maxeiner (5/14) Jun 03 2017 Quite, but if you backtrack to my initial statement, it was about
- Stanislav Blinov (28/46) Jun 03 2017 It's only true as long as you have full control of the source.
- Moritz Maxeiner (4/13) Jun 03 2017 It's always true, because I explicitly wrote *might*, not *will*,
- Stanislav Blinov (9/26) Jun 03 2017 Programmers and their tight-binding logic...
- Moritz Maxeiner (3/15) Jun 03 2017 That usually happens when two sides interpret an ambiguous
- Stanislav Blinov (45/69) May 29 2017 I've found this pattern works rather well:
C++ allows one to create types that are pointer types but wrap a primitive pointer to give RAII handling of resources. For example: class Dvb::FrontendParameters_Ptr { private: dvb_v5_fe_parms * ptr; public: FrontendParameters_Ptr(FrontendId const & fei, unsigned int const = verbose =3D 0, unsigned int const legacy =3D 0); FrontendParameters_Ptr(FrontendParameters_Ptr const &) =3D delete; FrontendParameters_Ptr & operator=3D(FrontendParameters_Ptr const = &) =3D delete; ~FrontendParameters_Ptr() {dvb_fe_close(ptr); } dvb_v5_fe_parms * c_ptr() const { return ptr; } dvb_v5_fe_parms * operator->() const { return ptr; } }; Has anyone any experience of doing the analogous thing idiomatically in D. I just re-realised I manually constructed a C++ abstraction layer around some of libdvbv5, so I am going to do the same for D. However whilst I (sort of) understand doing the wrapping with C++, I am not sure I have seen anyone wrapping C pointers with RAII in D. --=20 Russel. =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D Dr Russel Winder t: +44 20 7585 2200 voip: sip:russel.winder ekiga.n= et 41 Buckmaster Road m: +44 7770 465 077 xmpp: russel winder.org.uk London SW11 1EN, UK w: www.russel.org.uk skype: russel_winder
May 29 2017
On Monday, 29 May 2017 at 23:39:17 UTC, Russel Winder wrote:C++ allows one to create types that are pointer types but wrap a primitive pointer to give RAII handling of resources. For example: [...]std.stdio.File does basically the same thing with C's FILE*
May 29 2017
On Monday, 29 May 2017 at 23:39:17 UTC, Russel Winder wrote:C++ allows one to create types that are pointer types but wrap a primitive pointer to give RAII handling of resources. For example: class Dvb::FrontendParameters_Ptr { private: dvb_v5_fe_parms * ptr; public: FrontendParameters_Ptr(FrontendId const & fei, unsigned int const verbose = 0, unsigned int const legacy = 0); FrontendParameters_Ptr(FrontendParameters_Ptr const &) = delete; FrontendParameters_Ptr & operator=(FrontendParameters_Ptr const &) = delete; ~FrontendParameters_Ptr() {dvb_fe_close(ptr); } dvb_v5_fe_parms * c_ptr() const { return ptr; } dvb_v5_fe_parms * operator->() const { return ptr; } }; Has anyone any experience of doing the analogous thing idiomatically in D.Yes, I generally use structs with ` disable this(this)` and std.algorithm.mutation.move for this. Something like (untested, but general approach): --- module dvb; struct FrontendParameters_Ptr { private: dvb_v5_fe_parms* ptr; public: disable this(this); this(ref const FrontendId fei, const uint verbose = 0, const uint legacy = 0) { ... } ~this() { dvb_fe_close(ptr); } auto c_ptr() const { return ptr; } alias c_ptr this; } --- Be aware that the above deliberately prohibits normal struct copy construction, so there is always only a single struct object. "Borrow" it via ref / ref const or std.algorithm.mutation.move it to change the owner. Or wrap this struct itself in a lifetime management struct (such as std.typecons.RefCounted) if you need multiple owners.
May 29 2017
Thanks to Moritz and Stanislav for their examples, most useful. There are similarities (which I have just taken :-) but also some differences. Would one be considered more idiomatic D, or is it a question of different circumstances different approaches. The differences are mainly in construction I believe. =20 On Tue, 2017-05-30 at 00:31 +0000, Moritz Maxeiner via Digitalmars-d- learn wrote:[=E2=80=A6] =20 struct FrontendParameters_Ptr { private: =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0dvb_v5_fe_parms* ptr; public: =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 disable this(this); =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0this(ref const FrontendId fei, const uint v=erbose =3D 0, const=C2=A0uint legacy =3D 0) { ... } =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0~this() { dvb_fe_close(ptr); } =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0auto c_ptr() const { return ptr; } =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0alias c_ptr this; } ---On Tue, 2017-05-30 at 00:32 +0000, Stanislav Blinov via Digitalmars-d- learn wrote: [=E2=80=A6]=C2=A0 struct FrontendParametersPtr { =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0// No constructors, initialization wit=h parameters=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0// is done via the frontendParametersP=tr function=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 disable this(this); =C2=A0 =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0~this() =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0{ =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0// null check =is often useful to detect e.g.=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0// if this obj=ect has been `move`d=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0if (_ptr) dvb_=fe_close(_ptr);=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0} =C2=A0 =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0// with DIP1000, could also return `sc=ope`=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0inout(dvb_v5_fe_parms)* ptr() inout { =return _ptr; }=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0alias ptr this; package: =C2=A0 =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0void construct(/*your args here*/) { /=*...*/ }=C2=A0 private: =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0dvb_v5_fe_parms* _ptr; } =C2=A0 /// Replaces constructor, i.e. can be called with no arguments for /// replacing "default" construction of C++ auto frontendParametersPtr(Args...)(auto ref Args args) { =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0import std.functional : forward; =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0FrontendParametersPtr result =3D void; =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0result.construct(forward!args); =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0return result; // moves result, no cop=y is made} =C2=A0[=E2=80=A6] --=20 Russel. =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D Dr Russel Winder t: +44 20 7585 2200 voip: sip:russel.winder ekiga.n= et 41 Buckmaster Road m: +44 7770 465 077 xmpp: russel winder.org.uk London SW11 1EN, UK w: www.russel.org.uk skype: russel_winder
Jun 03 2017
On Saturday, 3 June 2017 at 17:40:32 UTC, Russel Winder wrote:Would one be considered more idiomatic D, or is it a question of different circumstances different approaches. The differences are mainly in construction I believe.Well, the differences I spot are: - null check in destructor: That's just because I forgot to add it. If you add ` disable(this)` (disable the default constructor), all elaborate constructors ensure it is not null, and no members can set it to null, you might be able to skip the check, but I may have missed some corner cases, so better be safe. - factory functions (Stanislav) vs. elaborate constructors (me): + If you don't need to be able to construct the object without arguments, it's a stylistic choice and I consider the elaborate constructors to be more idiomatic. + otherwise (i.e. you need to be able to construct the object without arguments), you need the factory functions, because elaborate constructors for structs cannot have zero arguments, as that would clash with the default constructor that must be computable at compile time (for the struct's `.init` value) - inout: You can use that in what I wrote, as well; that's just a shorthand way to write several functions that do the same thing: one for `const T`, one for `immutable T`, and one for `T`
Jun 03 2017
On 06/03/2017 09:06 PM, Moritz Maxeiner wrote:- null check in destructor: That's just because I forgot to add it. If you add ` disable(this)` (disable the default constructor), all elaborate constructors ensure it is not null, and no members can set it to null, you might be able to skip the check, but I may have missed some corner cases, so better be safe.`.init` is the corner case. `.init` is always there, even with ` disable this();`.
Jun 03 2017
On Saturday, 3 June 2017 at 19:21:58 UTC, ag0aep6g wrote:On 06/03/2017 09:06 PM, Moritz Maxeiner wrote:Of course, but AFAIK you'd need to explicitly assign it to an object, so `ptr` won't null by accident, but only by explicit programmer intent (same as overwriting the memory the object lives in via things like `memcpy`); and you can always screw things intentionally (you could also assign some invalid value to the pointer via `memcpy`). Are there any accidental corner cases?- null check in destructor: That's just because I forgot to add it. If you add ` disable(this)` (disable the default constructor), all elaborate constructors ensure it is not null, and no members can set it to null, you might be able to skip the check, but I may have missed some corner cases, so better be safe.`.init` is the corner case. `.init` is always there, even with ` disable this();`.
Jun 03 2017
On 06/03/2017 09:37 PM, Moritz Maxeiner wrote:Of course, but AFAIK you'd need to explicitly assign it to an object, so `ptr` won't null by accident, but only by explicit programmer intent (same as overwriting the memory the object lives in via things like `memcpy`); and you can always screw things intentionally (you could also assign some invalid value to the pointer via `memcpy`).I'd say `.init` can easily happen accidentally. Especially when ` disable this(this);` is involved. When you can't copy, you may have to move sometimes. But std.algorithm.move overwrites the old location with `.init`, assuming that `.init` can safely be destroyed. ---- struct S { void* ptr; disable this(this); ~this() { assert(ptr !is null); /* fails */ } } void f(S s) {} void main() { auto a = S(new int); import std.algorithm: move; f(move(a)); /* overwrites `a` with `S.init` */ } ----
Jun 03 2017
On Saturday, 3 June 2017 at 19:55:30 UTC, ag0aep6g wrote:On 06/03/2017 09:37 PM, Moritz Maxeiner wrote:Yep, that's exactly why I added the null check in the example. If the struct has a postblit or a destructor, `move` will be destructive, and will overwrite the source with .init. Sometimes it doesn't matter (i.e. free() is allowed to take a null pointer), but in general, for things like smart pointers where you'd do arbitrary access in destructor, it's a good habit to check for .init values first, in case the object has been moved.Of course, but AFAIK you'd need to explicitly assign it to an object, so `ptr` won't null by accident, but only by explicit programmer intent (same as overwriting the memory the object lives in via things like `memcpy`); and you can always screw things intentionally (you could also assign some invalid value to the pointer via `memcpy`).I'd say `.init` can easily happen accidentally. Especially when ` disable this(this);` is involved. When you can't copy, you may have to move sometimes. But std.algorithm.move overwrites the old location with `.init`, assuming that `.init` can safely be destroyed. ---- struct S { void* ptr; disable this(this); ~this() { assert(ptr !is null); /* fails */ } } void f(S s) {} void main() { auto a = S(new int); import std.algorithm: move; f(move(a)); /* overwrites `a` with `S.init` */ } ----
Jun 03 2017
On Saturday, 3 June 2017 at 19:55:30 UTC, ag0aep6g wrote:On 06/03/2017 09:37 PM, Moritz Maxeiner wrote:Calling std.algorithm.move is explicit programmer intent, I consider that about as accidental as calling memcpy with a source full of zeroes. In any case, having that check in the destructor is fairly cheap, so better safe than sorry (and include it).Of course, but AFAIK you'd need to explicitly assign it to an object, so `ptr` won't null by accident, but only by explicit programmer intent (same as overwriting the memory the object lives in via things like `memcpy`); and you can always screw things intentionally (you could also assign some invalid value to the pointer via `memcpy`).I'd say `.init` can easily happen accidentally. Especially when ` disable this(this);` is involved. When you can't copy, you may have to move sometimes. But std.algorithm.move overwrites the old location with `.init`, assuming that `.init` can safely be destroyed.
Jun 03 2017
On Saturday, 3 June 2017 at 20:13:30 UTC, Moritz Maxeiner wrote:Calling std.algorithm.move is explicit programmer intent, I consider that about as accidental as calling memcpy with a source full of zeroes. In any case, having that check in the destructor is fairly cheap, so better safe than sorry (and include it).Yes, it's explicit. Destructor call is still implicit though, and it better not be performing null dereference or something equally nasty :)
Jun 03 2017
On Saturday, 3 June 2017 at 20:25:22 UTC, Stanislav Blinov wrote:On Saturday, 3 June 2017 at 20:13:30 UTC, Moritz Maxeiner wrote:Quite, but if you backtrack to my initial statement, it was about ptr not being/becoming null (implicitly) in the first place, which *might* allow you to skip the check (if you don't set it to null via external means, such as memcpy, move, etc).Calling std.algorithm.move is explicit programmer intent, I consider that about as accidental as calling memcpy with a source full of zeroes. In any case, having that check in the destructor is fairly cheap, so better safe than sorry (and include it).Yes, it's explicit. Destructor call is still implicit though, and it better not be performing null dereference or something equally nasty :)
Jun 03 2017
On Saturday, 3 June 2017 at 20:53:05 UTC, Moritz Maxeiner wrote:On Saturday, 3 June 2017 at 20:25:22 UTC, Stanislav Blinov wrote:It's only true as long as you have full control of the source. Once you're using libraries and generic code, it's possible that it's out of your hands: import core.stdc.stdio; import std.exception; // external library struct RingBuffer(T,size_t size) { T[size] values = T.init; // the culprit // interface snipped... } // own code struct FileWrapper { FILE* file; disable this(); disable this(this); this(FILE* file) { enforce(file); this.file = file; } ~this() { fclose(file); } } void main() { // whoops, segfault RingBuffer!(FileWrapper,8) container; }On Saturday, 3 June 2017 at 20:13:30 UTC, Moritz Maxeiner wrote:Quite, but if you backtrack to my initial statement, it was about ptr not being/becoming null (implicitly) in the first place, which *might* allow you to skip the check (if you don't set it to null via external means, such as memcpy, move, etc).Calling std.algorithm.move is explicit programmer intent, I consider that about as accidental as calling memcpy with a source full of zeroes. In any case, having that check in the destructor is fairly cheap, so better safe than sorry (and include it).Yes, it's explicit. Destructor call is still implicit though, and it better not be performing null dereference or something equally nasty :)
Jun 03 2017
On Saturday, 3 June 2017 at 21:16:08 UTC, Stanislav Blinov wrote:On Saturday, 3 June 2017 at 20:53:05 UTC, Moritz Maxeiner wrote:It's always true, because I explicitly wrote *might*, not *will*, to indicate that it depends on your use case. Your example is a common use case where you can't skip the check.Quite, but if you backtrack to my initial statement, it was about ptr not being/becoming null (implicitly) in the first place, which *might* allow you to skip the check (if you don't set it to null via external means, such as memcpy, move, etc).It's only true as long as you have full control of the source. Once you're using libraries and generic code, it's possible that it's out of your hands:
Jun 03 2017
On Saturday, 3 June 2017 at 21:39:54 UTC, Moritz Maxeiner wrote:On Saturday, 3 June 2017 at 21:16:08 UTC, Stanislav Blinov wrote:Programmers and their tight-binding logic... - Honey, please buy a loaf of bread. If there are eggs, buy a dozen. ... - Hello, do you have eggs? - Yes. - Dozen loaves of bread, please. ;)On Saturday, 3 June 2017 at 20:53:05 UTC, Moritz Maxeiner wrote:It's always true, because I explicitly wrote *might*, not *will*, to indicate that it depends on your use case. Your example is a common use case where you can't skip the check.Quite, but if you backtrack to my initial statement, it was about ptr not being/becoming null (implicitly) in the first place, which *might* allow you to skip the check (if you don't set it to null via external means, such as memcpy, move, etc).It's only true as long as you have full control of the source. Once you're using libraries and generic code, it's possible that it's out of your hands:
Jun 03 2017
On Saturday, 3 June 2017 at 21:46:45 UTC, Stanislav Blinov wrote:On Saturday, 3 June 2017 at 21:39:54 UTC, Moritz Maxeiner wrote:That usually happens when two sides interpret an ambiguous statement ;)It's always true, because I explicitly wrote *might*, not *will*, to indicate that it depends on your use case. Your example is a common use case where you can't skip the check.Programmers and their tight-binding logic... - Honey, please buy a loaf of bread. If there are eggs, buy a dozen. ... - Hello, do you have eggs? - Yes. - Dozen loaves of bread, please. ;)
Jun 03 2017
On Monday, 29 May 2017 at 23:39:17 UTC, Russel Winder wrote:C++ allows one to create types that are pointer types but wrap a primitive pointer to give RAII handling of resources. For example: class Dvb::FrontendParameters_Ptr { private: dvb_v5_fe_parms * ptr; public: FrontendParameters_Ptr(FrontendId const & fei, unsigned int const verbose = 0, unsigned int const legacy = 0); FrontendParameters_Ptr(FrontendParameters_Ptr const &) = delete; FrontendParameters_Ptr & operator=(FrontendParameters_Ptr const &) = delete; ~FrontendParameters_Ptr() {dvb_fe_close(ptr); } dvb_v5_fe_parms * c_ptr() const { return ptr; } dvb_v5_fe_parms * operator->() const { return ptr; } }; Has anyone any experience of doing the analogous thing idiomatically in D. I just re-realised I manually constructed a C++ abstraction layer around some of libdvbv5, so I am going to do the same for D. However whilst I (sort of) understand doing the wrapping with C++, I am not sure I have seen anyone wrapping C pointers with RAII in D.I've found this pattern works rather well: module frontendparametersptr; struct FrontendParametersPtr { // No constructors, initialization with parameters // is done via the frontendParametersPtr function disable this(this); ~this() { // null check is often useful to detect e.g. // if this object has been `move`d if (_ptr) dvb_fe_close(_ptr); } // with DIP1000, could also return `scope` inout(dvb_v5_fe_parms)* ptr() inout { return _ptr; } alias ptr this; package: void construct(/*your args here*/) { /*...*/ } private: dvb_v5_fe_parms* _ptr; } /// Replaces constructor, i.e. can be called with no arguments for /// replacing "default" construction of C++ auto frontendParametersPtr(Args...)(auto ref Args args) { import std.functional : forward; FrontendParametersPtr result = void; result.construct(forward!args); return result; // moves result, no copy is made } ///----- module app; import frontendparametersptr; void main() { auto ptr = frontendParametersPtr(/* your args here */); } The main idea is that construction is handled by the `construct` function (which could be overloaded), instead of `this(...)` constructors: this way client code would either get default-initialized (.init) pointers, or those constructed with appropriate arguments (even with no arguments, if such is needed). Disabling copying is obvious. The rest depends on taste and purpose.
May 29 2017