Jump to page: 1 2
Thread overview
RAII pointers
May 29, 2017
Russel Winder
May 30, 2017
Nicholas Wilson
May 30, 2017
Moritz Maxeiner
Jun 03, 2017
Russel Winder
Jun 03, 2017
Moritz Maxeiner
Jun 03, 2017
ag0aep6g
Jun 03, 2017
Moritz Maxeiner
Jun 03, 2017
ag0aep6g
Jun 03, 2017
Stanislav Blinov
Jun 03, 2017
Moritz Maxeiner
Jun 03, 2017
Stanislav Blinov
Jun 03, 2017
Moritz Maxeiner
Jun 03, 2017
Stanislav Blinov
Jun 03, 2017
Moritz Maxeiner
Jun 03, 2017
Stanislav Blinov
OT: RAII pointers
Jun 03, 2017
Moritz Maxeiner
May 30, 2017
Stanislav Blinov
May 30, 2017
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.

-- 
Russel. ============================================================================= Dr Russel Winder      t: +44 20 7585 2200   voip: sip:russel.winder@ekiga.net 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 30, 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 30, 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 30, 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.
June 03, 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.


On Tue, 2017-05-30 at 00:31 +0000, Moritz Maxeiner via Digitalmars-d- learn wrote:
> […]
> 
> 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;
> }
> ---

On Tue, 2017-05-30 at 00:32 +0000, Stanislav Blinov via Digitalmars-d-
learn wrote:
[…]
>
> 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
> }
>
[…]
-- 
Russel. ============================================================================= Dr Russel Winder      t: +44 20 7585 2200   voip: sip:russel.winder@ekiga.net 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

June 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`

June 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();`.
June 03, 2017
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?
June 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` */
}
----
June 03, 2017
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.
« First   ‹ Prev
1 2