www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - on structs by reference

reply Alex <sascha.orlov gmail.com> writes:
Hi everybody,
I have the following question about "by reference" behavior 
related to structs.

I have a struct, say S, which contains a member of reference type
struct S
{
     int[] member;
}

and I have a main, for testing:

void main()
{
	S s; // = new S();
	s.arr = [1,2,3];
	writeln(s.arr); //first
	
	S t; // = new S();
	t = s;
	writeln(t.arr); //second
	
	s.arr ~= 4;
	writeln(s.arr); //third
	writeln(t.arr);
}

So, I create a first var of type S, then the second and copied 
the first into the second. The behavior is like intended, the 
array inside the struct is copied. But this is a deep copy, as 
the third block shows. If I change the array in the 's' var, it 
is not propagated into the 't' var.

What I want to do is: the propagation of changes to all vars, 
which are copies of the first one.

The idea which works:
change the definition of S into class and add 'new' on defining 
the vars.
Then, the example shows the desired behavior.

The second idea: implement a postblit into the struct S.
The problem hereby is, how to reference the unique var more then 
once? How does the postblit looks like? Is it simple a  disable 
this(this)? But then, I couldn't copy the vars at all... but 
indeed I don't want to copy them, I rather would like to 
reference them, if they are intended to mirror the same internal 
value. Is this idea possible at all?

And last but not least, the question of semantic: how to decide 
which of the two ways is the right one, if both are possible?
Nov 14 2015
parent reply Mike Parker <aldacron gmail.com> writes:
On Saturday, 14 November 2015 at 10:46:57 UTC, Alex wrote:
 Hi everybody,
 I have the following question about "by reference" behavior 
 related to structs.

 I have a struct, say S, which contains a member of reference 
 type
 struct S
 {
     int[] member;
 }

 and I have a main, for testing:

 void main()
 {
 	S s; // = new S();
 	s.arr = [1,2,3];
 	writeln(s.arr); //first
 	
 	S t; // = new S();
 	t = s;
 	writeln(t.arr); //second
 	
 	s.arr ~= 4;
 	writeln(s.arr); //third
 	writeln(t.arr);
 }

 So, I create a first var of type S, then the second and copied 
 the first into the second. The behavior is like intended, the 
 array inside the struct is copied. But this is a deep copy, as 
 the third block shows. If I change the array in the 's' var, it 
 is not propagated into the 't' var.
Actually, no, it isn't a deep copy. You seem to be missing the fundamentals of how slices (dynamic arrays) work in D. Read Steven's article at [1] for the details. Essentially, you can think of an array in D like this: struct Array(T) { size_t length; T* ptr; } When you assign s to t, this is what is being copied... *not* the contents of the array. Both arrays will still point into the same memory. Add these two lines immediately after t = s: writeln(s.arr.ptr); writeln(t.arr.ptr); You will see they print the same address. If you try something like this: s.arr[0] = 100; You will see the change reflected in both s and t. Add the same two calls to writeln after s ~= 4 and you will see the addresses are now different. Steven's article explains what's going on.
 What I want to do is: the propagation of changes to all vars, 
 which are copies of the first one.

 The idea which works:
 change the definition of S into class and add 'new' on defining 
 the vars.
 Then, the example shows the desired behavior.
For what you want to do, this is probably your best option. The other is work with S by pointer, but then you can still use S as a value type. Structs are value types, classes are reference types. If you never want the behavior of a value type for a given type, use a class.
 The second idea: implement a postblit into the struct S.
 The problem hereby is, how to reference the unique var more 
 then once? How does the postblit looks like? Is it simple a 
  disable this(this)? But then, I couldn't copy the vars at 
 all... but indeed I don't want to copy them, I rather would 
 like to reference them, if they are intended to mirror the same 
 internal value. Is this idea possible at all?
postblit is often used to make sure you get a deep copy of an array or any reference types: struct S { int[] arr; this(this) { arr = arr.dup; } } [1] http://dlang.org/d-array-article.html
Nov 14 2015
parent reply Alex <sascha.orlov gmail.com> writes:
On Saturday, 14 November 2015 at 11:48:42 UTC, Mike Parker wrote:

 So, I create a first var of type S, then the second and copied 
 the first into the second. The behavior is like intended, the 
 array inside the struct is copied. But this is a deep copy, as 
 the third block shows. If I change the array in the 's' var, 
 it is not propagated into the 't' var.
Actually, no, it isn't a deep copy. You seem to be missing the fundamentals of how slices (dynamic arrays) work in D. Read Steven's article at [1] for the details. Essentially, you can think of an array in D like this:
Yeah... you could be right... Just finished the awesome lecture, thank you for the link. So, let us speak in terms of content and topology, where by topology for the most part is meant the length of the array.
 struct Array(T) {
     size_t length;
     T* ptr;
 }
***
 When you assign s to t, this is what is being copied... *not* 
 the contents of the array. Both arrays will still point into 
 the same memory. Add these two lines immediately after t = s:

 writeln(s.arr.ptr);
 writeln(t.arr.ptr);

 You will see they print the same address. If you try something 
 like this:

 s.arr[0] = 100;

 You will see the change reflected in both s and t. Add the same 
 two calls to writeln after s ~= 4 and you will see the 
 addresses are now different. Steven's article explains what's 
 going on.
Yes. I tried this out. The difference seems clear now: If I try to alter the content at some place, where some slices point to, then all this slices will mirror the change. And if I alter* the slice itself, it is not said, that it will point to the same array. All right. *And by alter, I mean: changing the topology in a way that is incompatible with the previous underlying array.
 For what you want to do, this is probably your best option. The 
 other is work with S by pointer, but then you can still use S 
 as a value type. Structs are value types, classes are reference 
 types. If you never want the behavior of a value type for a 
 given type, use a class.
Yeah... this belongs to the conceptual part of my question. The difference between structs as value types and classes as reference types is more or less clear. The question is, how to define when a behavior is not a value type behavior any more.
 postblit is often used to make sure you get a deep copy of an 
 array or any reference types:

 struct S
 {
     int[] arr;
     this(this) {
         arr = arr.dup;
     }
 }
Ok, so this is far away from what I want. So now the question which I still have: I want to reduce my slice inside my struct 's' step by step. So, the only operations I want to make should not lead to a reallocation of the underlying buffer. But I want to control at which side (from the front, or from the back) of the slice the reducing step should be made and, if there are some other copies (or references) to the slice of the same size (same size and same beginning point, indeed) they should be reduced also. Some comments to myself: 1. Concerning ***: if the pointer and length are passed by value for each slice reference, then the simultaneous change is not possible, as the 'topology' is passed by value. 2. Indeed, the simultaneity itself speaks in favor of using a 'class S' instead of a 'struct S'. 3. The only point I stumble on is, that the main feature in my program is, that the underlying array, to which my slices refer to never changes. So, I'm going to have more and more slices, which are going to be smaller and smaller and each points to the same underlying array without overlapping, but at each step, there are as much copies of a single slice as elements it points to. I hope this last point was not too weird.
Nov 14 2015
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 11/14/2015 07:53 AM, Alex wrote:

 3. The only point I stumble on is, that the main feature in my program
 is, that the underlying array, to which my slices refer to never
 changes. So, I'm going to have more and more slices, which are going to
 be smaller and smaller and each points to the same underlying array
 without overlapping, but at each step, there are as much copies of a
 single slice as elements it points to. I hope this last point was not
 too weird.
Your struct objects must keep a reference to the slice that they use (i.e. one ore level of indirection). Here is a start: import std.stdio; /* This is the storage to the slices that objects will share. * * (Surprisingly, creating a slice dynamically is not possible due * to syntax issues: new int[N] means "allocates N ints and make * a slice from those." However, we need a dynamic slice object * here. I've decided to put the slices in a 'slice slice' here.) */ int[][] slices; struct S { size_t idx; ref int[] arr() { return slices[idx]; } } void main() { S s; // = new S(); slices ~= [1,2,3]; s.idx = slices.length - 1; writeln(s.arr); //first S t; // = new S(); t = s; writeln(t.arr); //second s.arr ~= 4; writeln(s.arr); //third writeln(t.arr); } [1, 2, 3] [1, 2, 3] [1, 2, 3, 4] [1, 2, 3, 4] Ali
Nov 15 2015
parent Alex <sascha.orlov gmail.com> writes:
On Monday, 16 November 2015 at 07:51:53 UTC, Ali Çehreli wrote:
 import std.stdio;

 /* This is the storage to the slices that objects will share.
  *
  * (Surprisingly, creating a slice dynamically is not possible 
 due
  * to syntax issues: new int[N] means "allocates N ints and make
  * a slice from those." However, we need a dynamic slice object
  * here. I've decided to put the slices in a 'slice slice' 
 here.)
  */
 int[][] slices;

 struct S
 {
     size_t idx;

     ref int[] arr()
     {
         return slices[idx];
     }
 }

 void main()
 {
     S s; // = new S();
     slices ~= [1,2,3];
     s.idx = slices.length - 1;
     writeln(s.arr); //first

     S t; // = new S();
     t = s;
     writeln(t.arr); //second

     s.arr ~= 4;
     writeln(s.arr); //third
     writeln(t.arr);
 }

 [1, 2, 3]
 [1, 2, 3]
 [1, 2, 3, 4]
 [1, 2, 3, 4]
Ok, this definitely helps. Thank you! For me, the key point here is, that my struct S, which use one of the slices in the Slice of slices does not own it. This is ok. So, I have to think about who owns the Slice of slices now, because only this 'something' should be able to rearrange its data... The cool thing is, that it will be able to modify the data THROUGH the s structs, as in the line s.arr ~= 4; and is not restricted to. So, new S structs are generated by operating on the Slice of slices directly and old slices can be modified by operating on the appropriate S struct. Thanks again for helping zooming out...
Nov 16 2015