On Sun, Sep 9, 2012 at 4:00 AM, Nick Wellnhofer <[email protected]> wrote:
> I've also been thinking about the fragile base class problem, and as food
> for thought I'd like to present what I consider the easiest solution.
Your general approach works for me, Nick.
> Note that my approach doesn't work with cross-parcel access of member
> variables, not even with accessing member vars of superclasses if the
> superclass comes from another parcel. But I think that's something we could
> live with.
That's a feature! :)
> Accessing the member vars of other classes is bad for encapsulation anyway.
Exactly. Right now, Clownfish only offers one option for member variable
access control: all members have `protected` exposure. Unfortunately,
allowing direct access to member variables with `protected` exposure causes
the same kinds of problems as allowing direct access by making a variable
`public`.
After this change, all member variables will be private by default, though
within a given parcel, you will have the option of enabling direct access
file-by-file.
> With this restriction, you only have to care about the total size of the
> member vars of a super class, not about the actual layout.
For what it's worth, we could define the macros I suggested in terms of yours.
#define Foo_num(self) \
(*((int*)SI_member_address(self, \
Foo_MEMBERS_OFFSET + offsetof(Foo, num))))
Since offsetof() evaluates to a compile-time constant, the additional math
would incur negligible runtime performance impact -- but we get to use one
OFFSET var per class rather than one per member variable.
> void
> Sub_method(void *self) {
> Sub_MEMBERS *members = Sub_get_members(self);
> members->sub_num = 1;
> members->sub_float = 1.0;
>
> // Only safe if Base is in the same parcel
> Base_MEMBERS *base_members = Base_get_members(self);
> base_members->base_num = 1;
> base_members->base_float = 1.0;
> }
I'd originally thought to avoid this approach because of the extra line of
code, but perhaps it's not so bad after all. Plus, there's an idiom for
single line access;
void
Foo_set_num(Foo *self, int num) {
Foo_MEMBERS(self)->num = num;
}
Bikeshedding questions: MEMBERS, or IVARS? Or both? Or something else?
`IVARS` -- an abbreviation of "instance variables" saves two letters. (Also
FWIW, in Java and C++ both "class variables" and "instance variables" are
"member variables" -- so "members" is a superset.)
void
Sub_method(void *self) {
Sub_IVARS *ivars = Sub_MEMBERS(self);
ivars->sub_num = 1;
ivars->sub_float = 1.0;
// Only safe if Base is in the same parcel
Base_IVARS *base_ivars = Base_MEMBERS(self);
base_ivars->base_num = 1;
base_ivars->base_float = 1.0;
}
`Sub_get_members` bothers me because it doesn't use the all caps convention
which we've informally reserved and therefore looks like userland code.
(`Foo_num` bothers me for the same reason.) Also the underscore in the type
name "Sub_MEMBERS" bothers me, but I guess SubMEMBERS isn't a good
alternative.
> The biggest drawback I can see is that objects become void pointers, and
> we'll lose some of the type-checking that the C compiler currently performs
> for us. But that's something other solutions to the fragile base class
> problem will also have to deal with.
Is using void pointers really mandatory? I'm not seeing it.
I think we can continue to use struct types for classes. We still get the
type checking benefits even if we never define the struct layout and leave the
type opaque.
void
Sub_method(Sub *self) {
Sub_MEMBERS *members = Sub_get_members(self);
members->sub_num = 1;
members->sub_float = 1.0;
// Only safe if Base is in the same parcel
Base_MEMBERS *base_members = Base_get_members((Base*)self);
base_members->base_num = 1;
base_members->base_float = 1.0;
}
In my view, that type checking is very important -- it's too easy to make
mistakes without it which can lead to severe memory errors.
Marvin Humphrey