Why are structs and classes so different?
Mike Parker
aldacron at gmail.com
Sun May 15 16:08:01 UTC 2022
On Sunday, 15 May 2022 at 15:26:40 UTC, Kevin Bailey wrote:
> I'm trying to understand why it is this way. I assume that
> there's some benefit for designing it this way. I'm hoping that
> it's not simply accidental, historical or easier for the
> compiler writer.
There's a problem that arises with pass-by-value subclasses
called "object slicing". Effectively, it's possible to "slice
off" members of superclasses. It's one of many pitfalls to be
avoided in C++. There you typically find the convention of
structs being used as POD (Plain Old Data) types (i.e., no
inheritance) and classes when you want inheritance, in which case
they are passed around to functions as references.
For reference:
https://stackoverflow.com/questions/274626/what-is-object-slicing
D basically bakes the C++ convention into the language, with a
class system inspired by Java.
>
> One problem that this causes is that I have to remember
> different rules when using them. This creates the additional
> load of learning and remembering which types are which from
> someone else's library.
Of all the complexity we need to remember as programmers, this is
fairly low on the totem pole. Simple rule: if you need (or want)
inheritance, use classes. If not, use structs.
>
> A bigger problem is that, if I have a struct that I suddenly
> want to inherit from, I have to change all my code.
You should generally know up front if you need inheritance or
not. In cases where you change your mind, you'll likely find that
you have very little code to change. Variable declarations, sure.
And if you were passing struct instances to functions, you'd want
to change the function signatures, but that should be the lion's
share of what you'd need to change. After all, D doesn't use the
`->` syntax for struct pointers, so any members you access would
be via the dot operator.
> In addition to that work, in both of these cases, one could
> easily do it wrong:
>
> // Fine with a struct, fatal with a class.
> Foo foo;
>
> At least in C++, the compiler would complain. In D, not even a
> warning.
You generally find out about that pretty quickly in development,
though. That's a good reason to get into the habit of
implementing and running unit tests, so if you do make changes
and overlook something like this, then your tests will catch it
if normal operation of the program doesn't.
>
> Why is it this way? What is the harm of putting a class object
> on the stack?
I've answered the "why" above. As to the the second question,
there's no harm in putting a class on the stack:
```d
import std.stdio;
class Clazz {
~this() { writeln("Bu-bye"); }
}
void clazzOnStack() {
writeln("Entered");
scope c = new Clazz;
writeln("Leaving");
}
void main()
{
clazzOnStack();
writeln("Back in main");
}
```
You'll find here that the destructor of `c` in `clazzOnStack` is
called when the function exits, just as if it were a struct.
`scope` in a class variable declaration will cause it to the
class to be allocated on the stack.
Note, though, that `c` *still* a reference to the instance. You
aren't manipulating the class instance directly. If you were to
pass `c` to a function `doSomething` that accepts a `Clazz`
handle, it makes no difference that the instance is allocated on
the stack. `doSomething` would neither know nor care. `c` is a
handle, so you aren't passing the instance directly and it
doesn't matter where it's allocated.
There's more to the story than just reference type vs. value
type. Structs have deterministic destruction, classes by default
do not (`scope` can give it to you as demonstrated above). [See
my blog post on the
topic](https://dlang.org/blog/2021/03/04/symphony-of-destruction-structs-classes-and-the-gc-part-one/) for some info. (And I'm reminded I need to write the next article in that series; time goes by too fast).
Everyone has their own criteria for when to choose class and when
to choose struct. For me, I default to struct. I consider
beforehand if I need inheritance, and if yes, then I ask myself
if I can get by without deterministic destruction. There are ways
to simulate inheritance with structs, and ways to have more
control over destruction with classes, so there are options
either way.
More information about the Digitalmars-d-learn
mailing list