www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - smart pointer for interior pointers

reply Steven Schveighoffer <schveiguy yahoo.com> writes:
I just was working on a project, and I had a need for two members of a 
struct to share state.

How could this work? A simple example:

struct S
{
    int x;
    int *y; // should refer to x
    this(int v)
    {
       x = v;
       y = &x;
    }
}

But this doesn't work on copying, because when you copy S, S.y is still 
pointing at the OLD S. Example:


S s1 = S(5);
S s2 = s1;
s1.x = 4;
writeln(*s2.y); // oops, writes 4, because s2.y is pointing at s1.x, not 
s2.x

But we can solve this with a postblit:

this(this) { y = &x;}

Although this works, there are issues:

1. y's target is defined by the type, it's impossible to rebind y at 
runtime, without having a copy mess that up.
2. Copying still requires executing a function to fixup the pointer.
3. Even if y's relative target isn't supposed to change, S has to know 
where to point y at relative to itself! For example, if S was inside a 
struct like this:

struct S2
{
    S s;
    int y; // I really want s.x to point at *this* y
}

Now, you need to add a postblit to S2 as well.

But with alias this, we can define a way to solve all these problems.

struct SPtr(T)
{
     ptrdiff_t _offset;
     void opAssign(T *orig) { _offset = cast(void *)orig - cast(void 
*)&this;}
     inout(T) *_get() inout { return cast(inout(T)*)((cast(inout(void) 
*)&this) + _offset);}
     alias _get this;
}

Basically, instead of storing the pointer, we store the offset to the 
struct itself. This works as long as the SPtr instance stays co-located 
with its target.

It has some advantages:

1. It can be defined at compile/initialization time:
struct S
{
    int x;
    SPtr!int y = SPtr(offsetFromytox); // not sure of an easy way to 
autocreate the offset :)
}

2. You can copy S anywhere, and every instance's y will point at the 
instance's x, without running any fixup code. You can even use memcpy.

The disadvantage, and I think there's only one, is that using SPtr must 
perform math that a straight pointer will not. I think in cases where 
this is needed, I prefer this disadvantage over the ones of other choices.

Thoughts? Does this seem like a good candidate for std.typecons?

If so, what's a good name? I want something kind of short :)

-Steve
May 27 2015
next sibling parent reply Artur Skawina via Digitalmars-d <digitalmars-d puremagic.com> writes:
On 05/28/15 01:31, Steven Schveighoffer via Digitalmars-d wrote:
 I just was working on a project, and I had a need for two members of a struct
to share state.
 
 How could this work? A simple example:
 
 struct S
 {
    int x;
    int *y; // should refer to x
    this(int v)
    {
       x = v;
       y = &x;
    }
 }
 
 But this doesn't work on copying, because when you copy S, S.y is still
pointing at the OLD S. Example:
 
 
 S s1 = S(5);
 S s2 = s1;
 s1.x = 4;
 writeln(*s2.y); // oops, writes 4, because s2.y is pointing at s1.x, not s2.x
 
 But we can solve this with a postblit:
 
 this(this) { y = &x;}
 
 Although this works, there are issues:
It doesn't. S f() { return S(42); }
 1. y's target is defined by the type, it's impossible to rebind y at runtime,
without having a copy mess that up.
 2. Copying still requires executing a function to fixup the pointer.
 3. Even if y's relative target isn't supposed to change, S has to know where
to point y at relative to itself! For example, if S was inside a struct like
this:
 
 struct S2
 {
    S s;
    int y; // I really want s.x to point at *this* y
 }
 
 Now, you need to add a postblit to S2 as well.
 
 But with alias this, we can define a way to solve all these problems.
 
 struct SPtr(T)
 {
     ptrdiff_t _offset;
     void opAssign(T *orig) { _offset = cast(void *)orig - cast(void *)&this;}
     inout(T) *_get() inout { return cast(inout(T)*)((cast(inout(void) *)&this)
+ _offset);}
     alias _get this;
 }
 
 Basically, instead of storing the pointer, we store the offset to the struct
itself. This works as long as the SPtr instance stays co-located with its
target.
auto a = s.y; // this 'a' now implicitly converts to 'int', but... void g(T)(T a); g(s.y); // ditto. artur
May 27 2015
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 5/27/15 6:21 PM, Artur Skawina via Digitalmars-d wrote:

 But with alias this, we can define a way to solve all these problems.

 struct SPtr(T)
 {
      ptrdiff_t _offset;
      void opAssign(T *orig) { _offset = cast(void *)orig - cast(void *)&this;}
      inout(T) *_get() inout { return cast(inout(T)*)((cast(inout(void)
*)&this) + _offset);}
      alias _get this;
 }

 Basically, instead of storing the pointer, we store the offset to the struct
itself. This works as long as the SPtr instance stays co-located with its
target.
auto a = s.y; // this 'a' now implicitly converts to 'int', but... void g(T)(T a); g(s.y); // ditto.
Yes, both your cases (including the one that I didn't quote) show that such constructs must be controlled privately. For example, S should really be written like this: struct S { int x; private SPtr!int _y; int *y() {return _y;} void y(int * newy) { _y = newy; } } And it gets kind of sticky from there if you wanted to replace an actual variable :) For example, y++. But you can get most of the abilities of a member and still not destroy the semantic of having it reference the copy. I actually need this in the project I'm writing, which I'm hoping to get into Phobos, and I'm either going to define it in that project, or define it in std.typecons. Maybe the best thing to do is to define it privately for that module, and then move it somewhere more public if it turns out to be something that's useful elsewhere. -Steve
May 28 2015
prev sibling parent reply ketmar <ketmar ketmar.no-ip.org> writes:
On Wed, 27 May 2015 17:31:32 -0600, Steven Schveighoffer wrote:

 But we can solve this with a postblit:
seems that you forgot about "move" semantics for structs. under some=20 conditions struct can be "moved", not "copied", so it `memcpy`ed and no=20 postblit will be called.=
May 27 2015
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 5/27/15 9:13 PM, ketmar wrote:
 On Wed, 27 May 2015 17:31:32 -0600, Steven Schveighoffer wrote:

 But we can solve this with a postblit:
seems that you forgot about "move" semantics for structs. under some conditions struct can be "moved", not "copied", so it `memcpy`ed and no postblit will be called.
Right, that is a drawback of the first form. You have to ensure that doesn't happen (which is why we say struct interior pointers are illegal). The real proposal doesn't have that issue, and that's actually the point of it :) -Steve
May 28 2015