For sometime now, I have been trying to wrap my head around the reason why some “cannibal” classes are allowed to compile.
Before I continue, perhaps I should explain what I call a “cannibal” class. Not sure if I just invented that term or if it has been around for a while or even if I am using it correctly but that is not important right now.
I basically call a cannibal class a class that consumes itself. In other words a class whose interface declares members of its own type. For example:
class Foo
{
public Foo SomeFoo;
}
As you can see above, class Foo has a member of type Foo (itself).
Now, the first time I saw this (looong time ago) I didn’t thing it was going to compile but it to my surprise it does compile. The reason why I didn’t thing this would compile is because to me this screams some type of recursive nightmare.
To complicate things a little further, I decided to try the same thing but making the class a struct such as:
struct Foo
{
public Foo SomeFoo;
}
Unfortunately, this does not compile, instead you get error: Struct member 'Foo.SomeFoo' of type 'Foo' causes a cycle in the struct layout
To me, a compile error makes more sense that no error but I am sure there most be a logical explanation to this behavior so I was wondering if anyone of you guys could explain this behavior.
Thanks.
The reason you can't design a struct like this is because structs have to be initialized when they are allocated with some default values. So, when you have a struct Foo
like you described and you create a Foo
...
Foo x; // x = default(Foo)
it calls the default constructor for Foo
. However, that default constructor has to give a default value for the Foo.SomeFoo
field.
Well, how's it find that? It has to call default(Foo)
. Which, in order to construct a Foo
, has to give a default value for the Foo.SomeFoo
field... As you surmised, this is a recursive nightmare.
Since you can create a null reference to a class, and refrain from actually creating an instance of the class immediately, there's no problem; you can call new Foo()
and Foo.SomeFoo
will just be null. No recursion necessary.
ADDENDUM: When you're thinking about this, if you're still confused, I think the other answers have another good way of considering it (same essential problem, different approach) -- when the program allocates memory for a Foo
, how's it supposed to do it? What's sizeof(Foo)
when Foo
contains some stuff, and then another whole Foo
? You can't do that.
Instead, if it were a class, it would just have to allocate a couple bytes for a reference to a Foo
, not an actual Foo
, and there's no problem.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With