Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannibal Classes

Tags:

c#

.net

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.

like image 591
Rene Avatar asked Mar 16 '09 18:03

Rene


1 Answers

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.

like image 70
mqp Avatar answered Sep 30 '22 03:09

mqp