www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Potential strategy for avoiding problems with copy of a struct

reply james.p.leblanc <james.p.leblanc gmail.com> writes:
Hello,

Question about a possible strategy to avoid problems with
undesired/unintended copying of a structure:

1) We have a struct, call this **Foo**.

2) We instantiate it with,   **x = Foo(a,b,c);**
        a.  our constructor will initialize a field:  **this.myadd 
= &this**
        b.  this capture the address of our original "x" in 
x.myadd.

3) We wish to allow, any function calls using x, so we cannot
    disable Foo's this(this).

4) Our goal is to avoid problems with any accidental copying
    of x ... say by doing:  **auto y=x;**

5) the copy of step 4 **does not use the constructor**, thus 
y.myadd
    would contain the address of x (and not y)

6) We can exploit that y.myadd does NOT contain its own
    address (but instead contains the address of x).

    This requires adding logic checks in any Foo opAssign,
    and other overloads.  For example, we can disallow any
    such overloads by checking:

               if( &this != this.myadd ){ ... }

7) Needing to operate on x with other functions implies that
    private or const is not a solution. (I believe.)

Some initial experiments lead me to believe this may acheive
part of what I would like.  But, it feels very "hackish" and
ugly.

Is there a better way?

Best Regards,
James
Aug 22 2021
next sibling parent Mathias LANG <geod24 gmail.com> writes:
On Sunday, 22 August 2021 at 07:58:12 UTC, james.p.leblanc wrote:
 Is there a better way?

 Best Regards,
 James
``` public mixin template NonMovableOrCopyable () { disable this (); disable this (this); disable ref typeof (this) opAssign () (auto ref typeof(this) rhs); } ``` This will catch most mistakes at CT. However the language is technically free to copy / move structs at will (and interior pointers are forbidden). A recent enough version of LDC (>= v1.20.0 IIRC, might be v1.22.0) will do a very good job at not needlessly moving things around. See for example the discussion here: https://forum.dlang.org/thread/miuevyfxbujwrhghmiuw forum.dlang.org DMD on the other hand is much more likely to not perform NRVO / move things, so be wary of compiler differences.
Aug 22 2021
prev sibling parent reply jfondren <julian.fondren gmail.com> writes:
On Sunday, 22 August 2021 at 07:58:12 UTC, james.p.leblanc wrote:
 Hello,

 Question about a possible strategy to avoid problems with
 undesired/unintended copying of a structure:

 1) We have a struct, call this **Foo**.

 2) We instantiate it with,   **x = Foo(a,b,c);**
        a.  our constructor will initialize a field:  
 **this.myadd = &this**
        b.  this capture the address of our original "x" in 
 x.myadd.
Sorry, I don't follow this at all. Consider: ```d struct Foo { int a, b, c; Foo* myadd; this(int a, int b, int c) { myadd = &this; this.a = a; this.b = b; this.c = c; } } void main() { import std.stdio : writeln; Foo x; // initialized with (0, 0, 0, null) writeln(&x); // a memory location x = Foo(1, 2, 3); // (1, 2, 3, &x) writeln(&x); // the exact same memory location writeln(x.myadd.a); // 1, rather than 0, because &x==x.myadd } ``` There's no saving the original "x" here, it's overwritten by the assignment. Are you sure you don't want classes instead, to get reference semantics?
 4) Our goal is to avoid problems with any accidental copying
    of x ... say by doing:  **auto y=x;**

 5) the copy of step 4 **does not use the constructor**, thus 
 y.myadd
    would contain the address of x (and not y)
`auto y=x;` actually does call object lifetime functions. ```d struct CountCopies { int copies; this(this) { // postblit copies++; } } unittest { CountCopies x; auto y = x; auto z = y; assert(x.copies == 0); assert(y.copies == 1); assert(z.copies == 2); } ``` or with a copy constructor, ```d struct CountCopies { int copies; this(ref return scope const CountCopies rhs) { // copy constructor copies = rhs.copies + 1; } disable this(this); // helps pre-copy-constructor compilers reject this code } unittest { CountCopies x; auto y = x; auto z = y; assert(x.copies == 0); assert(y.copies == 1); assert(z.copies == 2); } ```
 Some initial experiments lead me to believe this may acheive
 part of what I would like.  But, it feels very "hackish" and
 ugly.

 Is there a better way?
If you don't get an answer that you like, I suggesting posting functional code and then stating your dissastisfactions with it.
Aug 22 2021
parent reply james.p.leblanc <james.p.leblanc gmail.com> writes:
On Sunday, 22 August 2021 at 11:10:33 UTC, jfondren wrote:
 On Sunday, 22 August 2021 at 07:58:12 UTC, james.p.leblanc 
 wrote:
 Hello,
If you don't get an answer that you like, I suggesting posting functional code and then stating your dissastisfactions with it.
Mattias, jfondren, Thanks both for your replies, I always learn something from them. I've trimmed my code to a minimal example to give a better idea of my thinking on this. You will notice that to ensure that I "seed" x with its real address at initialization, I must add disable this() to my struct. import std.stdio; struct Foo { int a, b, c; Foo* myadd; this(int a, int b, int c) { this.a = a; this.b = b; this.c = c; this.myadd = &this; } disable this(); } void main() { // x is original, we wish to protect auto x = Foo(1, 2, 3); // y is the "bad copy", do not want operation on y to pollute original x auto y=x; writeln("&x: ",&x,", x.myadd: ",x.myadd,", the 2 agree, we have original!"); writeln("&y: ",&y,", y.myadd: ",y.myadd,", these 2 disagree (must be a bad copy!"); } Produces output: &x: **7FFC65C02CC8**, x.myadd: **7FFC65C02CC8**, the 2 agree, we have original! &y: 7FFC65C02CE8, y.myadd: **7FFC65C02CC8**, these 2 disagree (must be a bad copy! So, as stated, in my struct overloading I can check if my two values agree or not (exposing whether or not I have the original, or a copy), and react appropriately. I understand that the language allows circumvention of this method... but I just want to catch minor mistakes in programming. Again, all comments and thoughts are welcome. Best Regards, James
Aug 22 2021
next sibling parent reply jfondren <julian.fondren gmail.com> writes:
On Sunday, 22 August 2021 at 13:03:20 UTC, james.p.leblanc wrote:
 On Sunday, 22 August 2021 at 11:10:33 UTC, jfondren wrote:
 On Sunday, 22 August 2021 at 07:58:12 UTC, james.p.leblanc 
 wrote:
 Hello,
If you don't get an answer that you like, I suggesting posting functional code and then stating your dissastisfactions with it.
Mattias, jfondren, Thanks both for your replies, I always learn something from them. I've trimmed my code to a minimal example to give a better idea of my thinking on this.
I still really don't get what you're trying to do, to the point of wanting to know: are you making 'myadd' a field for this struct only so that you can check it against the address of the variable? Or is it a real example of data you want to protect from accidental copies that just, coincidentally, can also be used to check if the struct has been copied? And, still, are you sure that you want a struct and not a class? The idea of not polluting the original of a struct requires some reference types as members of the struct, or a pointer, as otherwise everything is copied and the original is perfectly 'protected' anyway. Here's a struct that 1. has an int* as an example of data that shouldn't be copied, for example because it would result in a double free on destruction 2. protects against bad copies, not by forbidding copies, but by nulling the int* with a postblit 3. can then report that it's an original vs. a copy by seeing if the int* is null ```d import std.stdio; import core.memory : pureMalloc, pureFree; struct Foo { int a, b, c; int* unique; this(int a, int b, int c) { this.a = a; this.b = b; this.c = c; unique = cast(int*) pureMalloc(int.sizeof); *unique = a * b * c; } void report() const { if (unique) writeln("I'm the original: ", *unique); else writeln("I am an incomplete copy :("); } this(this) { unique = null; } ~this() { pureFree(unique); } } void alwaysGetsACopy(Foo f) { assert(f.unique is null); } void reportIfOriginal(ref Foo f) { if (f.unique !is null) f.report; } void main() { auto x = Foo(1, 2, 3); auto y = x; x.report; // I'm an original y.report; // I'm a copy x.alwaysGetsACopy; // assertion succeeds x.reportIfOriginal; // I'm an original y.reportIfOriginal; } ```
Aug 22 2021
parent reply james.p.leblanc <james.p.leblanc gmail.com> writes:
On Sunday, 22 August 2021 at 13:37:50 UTC, jfondren wrote:

     this(this) {
         unique = null;
     }

     ~this() {
         pureFree(unique);
     }
 }
Dear jfondren, I truly appreciate you taking the time to help me with my question! **This bit of magic with the postblit may hold the key to my issue.** (I have done some experiments based on your example code, am learning much, and it looks VERY encouraging! It is a bit scary how you guessed very closely what I am trying to do. I have a AVX aligned pointers (obtained from fftw_malloc), that I want to protect. To be a bit more specific. The code that reads/writes from to/from fftw routines is all pointer based. But, to allow use of standard dlang things such as "foreach" and friends, I've been cobbling together a (naive) wrapper. This allows me to use standard dlang syntax and operator overloads. I call my struct "fakeArray" ... since it is meant to behave either as a static array, or a dynamic array as needed. It **seems** to work pretty well ... but protecting the arrays from accidental programming mistakes (such as the "auto y=x") has been eluding me. In fact, I **can** do a legitimate data copy via "y = x", as long as y is already instantiated as a "fakeArray". I must steady more the example you kindly provided (learn more about the postblit, and pureFree, the const report function, etc...) Best Regards, James
Aug 22 2021
parent jfondren <julian.fondren gmail.com> writes:
On Sunday, 22 August 2021 at 14:40:29 UTC, james.p.leblanc wrote:
 It is a bit scary how you guessed very closely what I am trying 
 to
 do.  I have a AVX aligned pointers (obtained from fftw_malloc), 
 that I
 want to protect.

 To be a bit more specific.  The code that reads/writes from 
 to/from fftw
 routines is all pointer based.  But, to allow use of standard 
 dlang things
 such as "foreach" and friends, I've been cobbling together a 
 (naive) wrapper.

 This allows me to use standard dlang syntax and operator 
 overloads.  I call my
 struct "fakeArray" ... since it is meant to behave either as a 
 static array,
 or a dynamic array as needed.

 It **seems** to work pretty well ... but protecting the arrays 
 from accidental
 programming mistakes (such as the "auto y=x") has been eluding 
 me.
OK, rather than roll your own solution, Unique!Foo might do what you want, from https://dlang.org/phobos/std_typecons.html#Unique
Aug 22 2021
prev sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 8/22/21 6:03 AM, james.p.leblanc wrote:

      struct Foo {
          int a, b, c;
          Foo* myadd;

          this(int a, int b, int c) {
              this.a = a;
              this.b = b;
              this.c = c;
              this.myadd = &this;
As Matthias Lang mentioned, keeping a reference to itself makes a struct object illegal in D. D sees structs as value types: It is supposed that any copy can be used in place of another copy. Ali
Aug 22 2021
parent james.p.leblanc <james.p.leblanc gmail.com> writes:
On Sunday, 22 August 2021 at 14:35:48 UTC, Ali Çehreli wrote:
 On 8/22/21 6:03 AM, james.p.leblanc wrote:

      struct Foo {
          int a, b, c;
          Foo* myadd;

          this(int a, int b, int c) {
              this.a = a;
              this.b = b;
              this.c = c;
              this.myadd = &this;
As Matthias Lang mentioned, keeping a reference to itself makes a struct object illegal in D. D sees structs as value types: It is supposed that any copy can be used in place of another copy. Ali
Ali, You highlight am important point that I have not been completely aware of. Indeed, I did have a few "warning bells" going off in my brain when I began typing "this.myadd = &this;" into my program ... but, it compiled, so I continued (into the potential quicksand, admittedly!) So, thanks for illuminating this issue for me. Also, the "postblit" hint recently posted in this thread, may allow me to procede along a safer path. Kind Regards, James
Aug 22 2021