Thread overview
Re: struct vs class
Nov 14, 2010
Jonathan M Davis
Nov 14, 2010
spir
Nov 14, 2010
Jonathan M Davis
November 14, 2010
On Sunday 14 November 2010 03:08:49 spir wrote:
> Hello,
> 
> 
> There seems to be 2 main differences between structs & classes:
> 1. structs instances are direct values, implement value semantics; while
> class instances are referenced (actually "pointed") 2. classes can be
> subtyped/subclassed in a simple way; structs cannot be really subtyped --
> but there is the "alias this" hack

The main thing to remember between structs and classes is that classes are polymorphic. If you want polymorphism, use a class. If you don't use a struct. Now, if you want a type which is _always_ a reference, then you should probably choose a class, but it's quite easy to have a struct which is a reference type by having its member variables are on the heap and don't get deep copied be a postblit constructor. It is more immediately clear, though, that a class is a reference type.

You can get more nuanced on the reasons why you should pick a struct or a class, but polymorphism is really what it generally comes down to. If you never intend to subclass it or have it implement interfaces, then it should probably be a struct.

- Jonathan M Davis
November 14, 2010
On Sun, 14 Nov 2010 03:32:18 -0800
Jonathan M Davis <jmdavisProg@gmx.com> wrote:

> On Sunday 14 November 2010 03:08:49 spir wrote:
> > Hello,
> > 
> > 
> > There seems to be 2 main differences between structs & classes:
> > 1. structs instances are direct values, implement value semantics; while
> > class instances are referenced (actually "pointed") 2. classes can be
> > subtyped/subclassed in a simple way; structs cannot be really subtyped --
> > but there is the "alias this" hack
> 
> The main thing to remember between structs and classes is that classes are polymorphic. If you want polymorphism, use a class. If you don't use a struct. Now, if you want a type which is _always_ a reference, then you should probably choose a class, but it's quite easy to have a struct which is a reference type by having its member variables are on the heap and don't get deep copied be a postblit constructor. It is more immediately clear, though, that a class is a reference type.
> 
> You can get more nuanced on the reasons why you should pick a struct or a class, but polymorphism is really what it generally comes down to. If you never intend to subclass it or have it implement interfaces, then it should probably be a struct.

Thank you, Jonathan.
But what about the copy issue. For instance, in a parsing lib, I currently have a "Source" class that is a kind of "cursored" text (think at a text file), so it's only:

class Source {
    string text;
    uint index;
    this (string text, uint index) {
        this.text = text;
        this.index = index;
    }
}

The advantages that match functions return a source that has stepped forward after successful match (hope you see what I mean, it's a common issue in parsing).
Conceptually, it's pure data, meaning it should be a struct. Also, as you point above, it requires no polymorphism. But I made it a class, because:
* It's created only once, so heavier creation on the heap is irrelevant.
* It's passed from match method to match method huge numbers of time (can be millions of times when parsing code). If I had a struct, it would be copied! (Even if the text itself can be prevented from copy by putting it in a plain array, or "pointed", the fields are still copied.)
* Higher-level patterns that delegate matching to sub-patterns directly get updated source (with new index), precisely because the source is referenced.

For instance, the actual match method of the Choice pattern type holds:
        // ...
        foreach (Pattern pattern ; this.patterns) {
            try
                return pattern.check(source);	// *******
            catch (MatchError e) {
                // ...
            }
        }
        // ...
    }

The marked line works because Source is referenced. Else, I would have to update source.index manually. Similarly, for a Composition pattern type (every sub-pattern must match in sequence):

        foreach (Pattern pattern ; patterns) {
            result = pattern.check(source);
            nodes ~= result.node;
        }

The source is automagically updated by each sub-pattern match.

Thus, this a case where semantic (of value) and feature (no polymorphism) rationales seem both to conflict with practical programming reasons.


Denis
-- -- -- -- -- -- --
vit esse estrany ☣

spir.wikidot.com

November 14, 2010
On Sunday 14 November 2010 04:14:29 spir wrote:
> On Sun, 14 Nov 2010 03:32:18 -0800
> 
> Jonathan M Davis <jmdavisProg@gmx.com> wrote:
> > On Sunday 14 November 2010 03:08:49 spir wrote:
> > > Hello,
> > > 
> > > 
> > > There seems to be 2 main differences between structs & classes:
> > > 1. structs instances are direct values, implement value semantics;
> > > while class instances are referenced (actually "pointed") 2. classes
> > > can be subtyped/subclassed in a simple way; structs cannot be really
> > > subtyped -- but there is the "alias this" hack
> > 
> > The main thing to remember between structs and classes is that classes are polymorphic. If you want polymorphism, use a class. If you don't use a struct. Now, if you want a type which is _always_ a reference, then you should probably choose a class, but it's quite easy to have a struct which is a reference type by having its member variables are on the heap and don't get deep copied be a postblit constructor. It is more immediately clear, though, that a class is a reference type.
> > 
> > You can get more nuanced on the reasons why you should pick a struct or a class, but polymorphism is really what it generally comes down to. If you never intend to subclass it or have it implement interfaces, then it should probably be a struct.
> 
> Thank you, Jonathan.
> But what about the copy issue. For instance, in a parsing lib, I currently
> have a "Source" class that is a kind of "cursored" text (think at a text
> file), so it's only:
> 
> class Source {
>     string text;
>     uint index;
>     this (string text, uint index) {
>         this.text = text;
>         this.index = index;
>     }
> }
> 
> The advantages that match functions return a source that has stepped forward after successful match (hope you see what I mean, it's a common issue in parsing). Conceptually, it's pure data, meaning it should be a struct. Also, as you point above, it requires no polymorphism. But I made it a class, because: * It's created only once, so heavier creation on the heap is irrelevant. * It's passed from match method to match method huge numbers of time (can be millions of times when parsing code). If I had a struct, it would be copied! (Even if the text itself can be prevented from copy by putting it in a plain array, or "pointed", the fields are still copied.) * Higher-level patterns that delegate matching to sub-patterns directly get updated source (with new index), precisely because the source is referenced.
> 
> For instance, the actual match method of the Choice pattern type holds:
>         // ...
>         foreach (Pattern pattern ; this.patterns) {
>             try
>                 return pattern.check(source);	// *******
>             catch (MatchError e) {
>                 // ...
>             }
>         }
>         // ...
>     }
> 
> The marked line works because Source is referenced. Else, I would have to update source.index manually. Similarly, for a Composition pattern type (every sub-pattern must match in sequence):
> 
>         foreach (Pattern pattern ; patterns) {
>             result = pattern.check(source);
>             nodes ~= result.node;
>         }
> 
> The source is automagically updated by each sub-pattern match.
> 
> Thus, this a case where semantic (of value) and feature (no polymorphism)
> rationales seem both to conflict with practical programming reasons.

Well, like I said, it can be more nuanced. Polymorphism is the main thing, but other factors can matter. In this case, you have an efficiency problem in using a struct. The are several ways around that:

1. Make the struct a reference type, making its internals pointers or references.

2. Make the struct a value type, but make its internals pointers are references and use COW (copy-on-write).

3. Use a pointer to a struct.

4. Use a class.

Personally, I think that #1 can be useful, but generally that's probably not what you want. #2 doesn't work correctly right now due to bugs regarding destructors, and it's a pretty complicated solution anyway.

As for #3 and #4, if you're not going to use polymorphism, then there really isn't much difference between using pointers to structs and making the type a class - the main difference between a pointer and a reference being that a reference is polymorphic (if you had a pointer to a class, it wouldn't be polymorphic), so it's arguably better to use pointer to a struct than a class when it doesn't need to be polymorphic, but I suppose that it's a matter of taste. If you make it a struct, you at least _can_ stick it on the stack if you want though. It also makes them easier to copy, since you don't have to declare a clone() function or something similar. However, making it a class makes it obvious that it's intended to be used as a reference type rather than a value type, so it all depends on what you're trying to do.

- Jonathan M Davis