Topic: Slots and member methods.
Author: JdeBP@jba.co.uk (Jonathan de Boyne Pollard)
Date: 1995/04/12 Raw View
Guus C. Bloemsma (guus@proxim.franken.de) wrote:
: I've been reading about Dylan lately. One of the things I really liked is
: "slots". For those who don't know about Dylan (or other languages that
: implement something similar, CLOS for example), slots are "virtual data
: members". This means you can assign to, and use, a data member that
: doesn't really exist. This works by having getter and setter functions
: that are called when the value of a data member is used or a new value is
: assigned to it.
What you need is DirectToSOM C++ :
class MyClass : public SOMObject {
public:
int a ;
pragma SOM_Attribute(a ; noset, noget, nodata, virtualaccessors) ;
} ;
`a' is now an `attribute' of class MyClass, with two (implicitly declared)
accessor methods which are called in situations like :
MyClass m ;
m.a = 1 ; // Compiler "secretly" generates call to m._set_a(1)
int b = m.a ; // Compiler "secretly" generates call to m._get_a()
Since I've taken the precaution of declaring the accessor methods as
virtual, I can override them in derived classes (for extra fun) :
class Derived : public MyClass {
public:
virtual int _get_a()
{
cout << "Derived is returning a" << endl ;
return MyClass::_get_a() ;
}
virtual void _set_a(int value)
{
cout << "Derived is setting a" << endl ;
MyClass::_set_a(value) ;
}
}
The `nodata' modifier that I have also chosen to use means that there isn't
actually any instance data storage to "back" the data member, meaning that
I also have to implement MyClass::_set_a() and MyClass::_get_a(int) to do
something under the covers to simulate the storage as necessary.
In a GUI class, for instance, I can have a `char * text' attribute, with
the nodata modifier, and use WinQueryWindowText and WinSetWindowText to
provide the "storage" for the text. Which can then be used as :
window.text = "This is the new window text" ;
In other words, there exist C++ implementations today that can actually
*do* what you want. I suggest that if you are *really* interested in
seeing these features in the C++ language that you vote with your wallet
and use those implementations. The vendors will soon realise where the
market is going.
FWIW, the DTS C++ compiler that I use (and I actually make use of attributes
and accessor methods in some of my code) is MetaWare High C++ 3.31a.
Author: guus@proxim.franken.de (Guus C. Bloemsma)
Date: 1995/04/12 Raw View
Hi,
I've been reading about Dylan lately. One of the things I really liked is
"slots". For those who don't know about Dylan (or other languages that
implement something similar, CLOS for example), slots are "virtual data
members". This means you can assign to, and use, a data member that
doesn't really exist. This works by having getter and setter functions
that are called when the value of a data member is used or a new value is
assigned to it.
Also, in "Design and Evolution of C++", there is a discussion about
operator.(), which feels awkward to me. I have a feeling that overriding
operator.() is like overriding addition in the CPU, it simply changes too
much.
Combining these two ideas, I think that what you should be able to
override (or add) is something like operator.member(), where "member" can
be any identifier. This would be something like a setter function.
The setter function would then be operator.member=().
Of course, if the member is actually declared as a data member, the getter
and setter functions are defaulted by the compiler, but can still be
overridden. This can be very useful if the containing class wants to know
when a member changes.
The main advantage is that you can keep the interface of your class a lot
simpler because you don't have to declare a whole lot of accessor
functions "just in case".
The setter function raises a new issue. Since there may have been a
previous definition for the operator=() of the class of the member, the
containing class is actually overriding a member function of a data
member!
This might actually be a worthwile feature. Imagine a set of classes that
represent filters that can stream data between them. A simple approach
might be to have two classes InputConnection and OutputConnection. A
compression filter would inherit from both and then override
InputConnection::TakeData() and OutputConnection::SupplyData().
This approach fails as soon as you want to write an AdditionFilter which
has two InputConnections and therefore needs to inherit twice from it. Of
course it COULD be done using intermediate classes and a lot of renaming,
but that becomes awkward very fast.
In fact what happened here is that you have a HAS-A relationship
conceptually, but you have to inherit in order to be able to override. So
what you really want is a HAS-A-CUSTOM-VERSION-OF relationship:
class AdditionFilter {
public:
InputConnection inputA;
InputConnection inputB;
OutputConnection output;
void inputA.TakeData(void *data) {...};
void inputB.TakeData(void *data) {...};
void *output.SupplyData() {...};
...
void DoSomeFlushing();
};
Now if you find out that you need to do some flushing whenever one of the
inputs is reconnected, you can just "trap" on that:
void inputA.ConnectTo(OutputConnection *newOutput) {
DoSomeFlushing();
inputA.InputConnection::ConnectTo(newOutput);
};
or even:
void inputA.operator = (const InputConnection
&newInput) {
DoSomeFlushing();
inputA.InputConnection::operator = (newInput);
};
Note that even though this method overrides a method of member inputA, its
definition's this pointer is of type AdditionFilter*, so it can use the
whole containing object (that's the point after all).
Enough for today, please tell me what you think about this, and send me a
copy of your followup by email since I'm having problems with my newsfeed
(I had to repost this a couple of times, so I apologize if anybody got
this multifold).
Have fun, Guus.