www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - RAII pointers

reply Russel Winder via Digitalmars-d-learn <digitalmars-d-learn puremagic.com> writes:
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
next sibling parent Nicholas Wilson <iamthewilsonator hotmail.com> writes:
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
prev sibling next sibling parent reply Moritz Maxeiner <moritz ucworks.org> writes:
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
parent reply Russel Winder via Digitalmars-d-learn <digitalmars-d-learn puremagic.com> writes:
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=A0
 uint 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
parent reply Moritz Maxeiner <moritz ucworks.org> writes:
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
parent reply ag0aep6g <anonymous example.com> writes:
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
parent reply Moritz Maxeiner <moritz ucworks.org> writes:
On Saturday, 3 June 2017 at 19:21:58 UTC, ag0aep6g wrote:
 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();`.
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?
Jun 03 2017
parent reply ag0aep6g <anonymous example.com> writes:
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
next sibling parent Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Saturday, 3 June 2017 at 19:55:30 UTC, ag0aep6g wrote:
 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` */ } ----
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.
Jun 03 2017
prev sibling parent reply Moritz Maxeiner <moritz ucworks.org> writes:
On Saturday, 3 June 2017 at 19:55:30 UTC, ag0aep6g wrote:
 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.
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).
Jun 03 2017
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
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
parent reply Moritz Maxeiner <moritz ucworks.org> writes:
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:

 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 :)
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).
Jun 03 2017
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
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:
 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 :)
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: 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; }
Jun 03 2017
parent reply Moritz Maxeiner <moritz ucworks.org> writes:
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:
 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:
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.
Jun 03 2017
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
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:
 On Saturday, 3 June 2017 at 20:53:05 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).
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:
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
parent Moritz Maxeiner <moritz ucworks.org> writes:
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:
 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. ;)
That usually happens when two sides interpret an ambiguous statement ;)
Jun 03 2017
prev sibling parent Stanislav Blinov <stanislav.blinov gmail.com> writes:
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