Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Mixins (long)

2 views
Skip to first unread message

John MAX Skaller

unread,
Jan 28, 1993, 2:23:52 PM1/28/93
to
MIXIN SOFTWARE TECHNOLOGY
=========================

Here is a long promised description of mixins. Sorry,
there are no real examples here. Its up to your imagination
until I get something real prepared. But this description
couldn't wait.

I hope it helps.

John MAX Skaller, Maxtal P/L, Sydney, Australia.
max...@extro.ucc.su.oz.au

QUESTION: What are mixins?

ANSWER: "Mixin" in C++ is a high power technique for using
multiple inheritance (MI) with abstract virtual bases (AVB)
to enable incremental development of both interfaces and implementations
of classes.

To understand mixins we first need to know the basic enabling
technology. I've often commented mixins were Bjarnes greatest
achievement in C++: the following two questions outline
that achievement. Note Bjarne claims it wasn't intended
to enable mixin technology: so much the greater is a
scientific law that has unexpected and useful consequences.

QUESTION: How do mixins work?

ANSWER: Mixins rely on the "sibling call" mechanism described in
the ARM on p234:

"A call to a virtual function through one path
in an inheritance structure may result in the invocation
of a function redefined on another path.
This is an elegant way for a base class to
act as a means of communication between sibling classes."

B f()=0
/\
/ \
f(); X Y g(){ f(); }
\ /
\/
D

The virtual base B in this diagram contains a 'hook' or 'vector'
function 'f'. The definition of 'f' is supplied in X, while
'f' is used by 'g' in Y. The class D is derived from X and Y
to 'connect' the definition of 'f' in X with the usage of
'f' in Y via their shared virtual base B.

QUESTION: Why does Sibling Call work?

ANSWER: Sibling call works because of the Name Dominance Rule,
described in the ARM in 10.1.1 on pp204-205:

"A name X::f dominates a name B::f if its class X has
B as a base. If a name dominates another no ambiguity
exists between the two; the dominant name is used
when there is a choice ...

"The dominance rule is necessary for virtual functions
since it *is* the rule for which function should be
invoked for a virtual call ... for virtual functions
the dominance rule is what guarrantees that the
same function is called independently of the static
type of the pointer, reference, or name of the object
for which it is called"


Axiom Classes
-------------

The base classes in mixin technology are usually small, independent,
and abstract: ideal for placement in a library. The member
functions of these classes are usually small in number,
independent, and pure virtual, and may be called axioms.

class Ax1 class Ax2 class Ax3

A set of global functions operating on the base abstractions
can be written and also placed in the library:
they will work for all implementations of the abstractions
because of polymorphism. Note that these functions
are like 'theorems' in that they are defined in terms
of the undefined pure virtual axioms.


g(Ax1&); g(Ax2&); g(Ax3&);
h(Ax1*); h(Ax2*); h(Ax3*);

k(Ax1*,Ax2*);

Although global, these functions do NOT pollute the global namespace,
since they are qualified by the classes they operate on:
they are as attached to the class as its members, but have
no access to implementation details.

The axiom classes can be combined using public virtual inheritance
to form larger, convenient, sets of axioms.

Theorem (Interface) Classes
---------------------------

It is common practice to put a set of related 'theorem'
functions in a 'Theorem' class derived from the axiom classes
to which it refers. If more than one class is refered
to, MI must be used. The inheritance is usually public,
although sometimes it is protected or private if the
axioms are considered too primitive for end-user invokation.
The inheritance will almost always be virtual, so that
subsequent combination of theorem classes by MI will
always only ever have one instance of each axiom class.

These classes are sometimes called 'Interface' classes,
since they contain higher level functionality than the
primitive axiom classes, which is more suitable for
use by end-users.

In general, various schemes of building on the base abstractions
can be used, or no scheme at all. It is not incorrect to
introduce new axioms into theorem classes, for example.

Note that in advanced mixin use, interface classes may themselves
be introduced initially as abstract classes. Their pure
virtuals are then defined in terms of some set of axioms
by creating an implementation class which derives from both
the interface class and the axiom classes, and implements
the interface functions in terms of the axiom functions.
The resultant interface is still abstract: but the abstraction
has been shifted from the high level interface functions
to the low level axiom primitives. Subsequent definition
of the primitives 'concretises' the interface class.


Model (Implementation) Classes
------------------------------

For each base abstraction, a typical library will contain several
implementations with different properties. An implementation
of an abstract class is derived *virtually* from the abstraction,
and implements it by overriding the virtual functions.

Typically, one implementation will be optimised for speed, and another
to use the least memory. However, the implementations
may also differ in their semantics, for example the pure
virtual function 'Draw()' may be overriden in different
classes to draw different shapes.

The implementation of an axiom class is called a Model or
Implementation class. As above, in a non-strict scheme,
Model classes may implement only some of the pure virtual
functions, and subsequent derivations may be needed to
completely concretise the abstraction. Such derivations
may also inherit other abstractions in order to concretise
the first set, transfering the burden of definition
sideways.

Constructor (Mixin) Classes
---------------------------

Now when you want to create an object which has a certain
interface, you select, or build using MI, an appropriate
interface class for your application. This class must
be globally known, and is the class with which
you perform all work.

Luckily, you dont have to write many procedures that operate
on this class: they are already in the library, although
they are typically defined to work on the bases: you can
still call them because your interface class 'isA' base,
for each of its bases.

However, the interface class is abstract, you cant
make an object of the interface class directly.
Instead you must 'mixin' <finally!> an implementation
of each axiom base by multiple virtual inheritance.

Suppose you have an interface class IF which has axiom
classes Ax1, Ax2 and Ax3. You choose to use the
implementations Ax1Im3, Ax2Im7 and Ax3Im2, because these
are optimised for speed and support the semantics you desire.
Here's how you do the mixin:

IF * makeIF()
{
class Dummy : public IF,
public virtual Ax1Im3,
public virtual Ax2Im7,
public virtual Ax3Im2
{};
return new Dummy;
}


QUESTION: Where's the CODE??

ANSWER: You already wrote it. Mixins embody the idea
of reusability to the fullest possible extent.
Mixins are for lazy programmers --- the best kind :-)

You may have to write a constructor, though, because
constructors are not inherited. :-(

Thats why using mixins is sometimes called 'Programming
by Constructor': the constructor is the only code
you often have to write.

QUESTION: Why is the mixin class declared in function local scope?

ANSWER: It would be even better to use an anonymous class:
the name of this class is not important and should not be known
by the rest of the system. It is, after all, a class
created purely for the purposes of implementing the interface:
and it is good practice to hide implementation details.

It is often crucial that these details are hidden, and cannot
be retrieved *even* with nasty hacks like dynamic downcasting.
This is because the implementations that have been hooked
in are too primitive for client access, and their direct
use by the end user may destroy the integrity of the object.

Making the constructor class local or anonymous defeats
downcasting, an excellent idea: the object should only
be accessed through its declared interface.

Perhaps unfortunately it is sometimes necessary to use
public derivation of the implementations so that
their constructors and destructors are accessible.
It is also usually necessary to name the concrete class
since you cannot define a constructor for the class
otherwise.

If the constructor class cannot be hidden by making it
local, its a good idea to hide it by declaring it
in file scope only.

Such definitions of the constructor class are
called 'On-The-Fly', because you make up the class
on the spot, just before you create an instance of it,
purely for the purposes of creating that instance,
and then forget the class immediately after the object
has been constructed.

Usually, the only new functions you can sensibly add to a mixin
class are those of *the* constructor and destructor,
and any helpers need by those functions. Only one constructor
is needed, since you only construct one object immediately
after mixin defintion: you might as well use the default
constructor.

It is also possible that you have selected or built an interface
class that is still partially abstract. In this case
you must also override the remaining pure virtual functions
in the mixin class. It is also possible to override
other virtual functions 'At The Last Minute' to provide
more efficient access or modified functionality.

QUESTION: Is this scheme of Axiom (primitive), Theorem(Interface),
Model(Implementation) and Constructor(Mixin) classes
the only way to do mixins?

ANSWER: No. You can do anything you want :-)
The scheme outlined above is intended to indicate the 'Spirit of Mixin',
and not any set of hard and fast rules.

The basic principle of mixin is simply that you should defer
binding of the implementation of an interface until
the last possible place, and that the basic functional
primitives be encapsulated in small, independent abstract
classes.

QUESTION: Is there an even more powerful technique than mixin?

ANSWER: Yes and No :-)

YES: Delegation is more powerful than mixin.
With delegation the 'base' classes are accessed via
a pointer (or reference) to another object, and the
construction and destruction of these 'base' objects
are under programmer control.

NO: With mixin, there are also (in many implementations)
hidden pointers to virtual base sub-objects in the classes:
these pointers are needed to locate the virtual bases.
However these hidden pointers are initialised and accessed
correctly at all times because they are manipulated directly
only by the compiler, not the programmer.

Mixin is the most powerful statically secure method available
in C++.

QUESTION: Can you combine mixins and delegation?

ANSWER: It is not at all uncommon to combine the two techniques.
Mixins are prefered where possible because they are statically
and dynamically secure, and because construction and destruction
are managed automatically.

QUESTION: Is the use of virtual bases necessary in mixins?

ANSWER: Absolutely. Both the interface and implementation
classes inherit from the axioms: one to build on the
primitives, and the other to define them. If you
didnt use virtual inheritance in both cases, the definitions
would not be called by the interface functions:


Ax f()=0 Axiom: MUST be shared
/\
virtual mandatory ---> / \ <---- virtual mandatory
/ \
Implementation f(); Im If g(){ f(); } Interface
\ /
\/
Mx Mixin


QUESTION: Why does mixin enable incremental development?

ANSWER: Because you can provide, and use as you desire,
implementations and abstractions in small pieces,
independently. The binding of the implementation pieces
is defered to the last minute: on the fly construction
from the pieces.

This cannot be done with single inheritance (SI):
with SI you get 'layered development', in which
binding is done in layers.

Of course, delegation also provides incremental development,
and is more powerful than mixin, so arguing that
only SI and delegation is needed is pointless: mixin
is the technology that provides greatest power while
remaining statically secure.

QUESTION: Can I use mixins with templates?

ANSWER: Oh YES. Please do.

QUESTION: Whats the best way to build a class library?

ANSWER: With mixins of course. Delegation may also
be required for more advanced techniques. However,
delegation is not as well supported or secure as
mixin in C++: use mixins wherever possible.

QUESTION: Whats the worst way to build a library?

ANSWER: Thats a leading question :-) The worst way
is to use an 'Object Heirarchy' which has a single
class 'Object' from which everything is derived by SI
in increasing levels of complexity.

This inevitably leads to either a VERY fat interface for
the 'Object' class in order to preserve polymorphic access via
virtual functions, or use of downcasting to emulate dynamism.

Both these methods are ugly in C++, and break all the known
principles of good modular design:

o Small interfaces
o Weak coupling
o Explicit coupling
o Hiding of implementation
o Closure of modules to changes
o Openness of modules to extension
o Reusability


Those not believing this should try to combine the use
of several Object based class libraries. The original
Borland and NIH libraries are examples.

QUESTION: Why dont mixins have this problem?

ANSWER: Because a mixin library is based on a set of small,
independent abstractions. Using two mixin libraries together
is just a case of taking the union of the abstractions.


QUESTION: Does use of mixins lead to an exponential explosion
of classes?

ANSWER: Not necessarily, it should do precisely the opposite.
The sets of basic abstractions and their implementations
can be combined by a simple union: the numbers of
such classes is just a simple addition --- its linear not
exponential.

Certainly, the number of *possible* combinations you can
have with mixins is exponential --- its supposed to be!
This is the whole point: you can have a very wide choice
of interfaces, yet implement them by a simple mixin
of some set of implementation classes.

But these choices are made 'on the fly': the existant
combinations in any one program will always be small,
and the differing implementation choices are irrelevant
because they're hidden.

Again, the number of useful interface classes you can
form will be exponential on the axiom classes, and this
is desirable. You dont have to actually create every such
combination.

Mixin libraries should be considered a toolkit of reusable
components, together with a few common combinations of these,
supplied for convenience.

A mixin library is not a monolithic, integrated, tightly
coupled system like an 'Object Heirarchy'.

QUESTION: Are mixin libraries hard to design?

ANSWER: Yes, all reusable components are hard to design.
It is not easy to decide exactly how to divide up functionality
between classes.

QUESTION: Are mixin libraries hard to use?

ANSWER: No, they're quite easy to use: often you need only
derive an appropriate class, mixing in the desired implementation
components, and possibly writing a constructor.

However, you should note that a mixin library does not
present you with a total solution to your problem. It still
leaves the task of designing solutions and implementing
them up to you, the programmer. It presents a wide variety
of solutions: you have much more choice than with an Object
Library.

What the mixin library does is save you writing code: you will
spend more time deciding which classes to use, and how to
plug them together than coding: the code is already written.

When you do have to write code, its usually to supply a new
implementation of an axiom class. In that case, the constraints
are extremely tight: the interface to which you must
conform is well defined, and the coding job is usually trivial.

You can, and will, of course, create new axiom classes,
some more application specific than others. Mixins are
wonderful in supporting this, because you only have
to provide the application specific interface and
implementation details, you can reuse other components
togther with your application axioms just as you
can when using the library classes: by using
MI to combine your classes with those in the library
to provide more complete application interfaces,
and by using mixins to construct them.


QUESTION: How will mixin libraries change my coding style?

ANSWER: You wont spend much time writing code. Your effort
will be directed towards design rather than hacking.

QUESTION: Are there any good mixin libraries available?

ANSWER: Not that I'm aware of. There are several reasons:

1) Early C++ didnt have mixins: there was no
MI, only SI. People still use class libraries
designed at this time.

2) Many existing compilers have or had until recently,
bugs in the implementation of MI,
abstract classes and virtual functions.

For example, Cfront version 3 incorrectly diagnosed
mixin classes as abstract, defeating the use of mixins.
Version 3.0.1 is fixed, I believe.

3) Mixin technology is not well understood.
Thats why I've written this article.

Mixins use some of the newest concepts in C++:
Abstract classes, Multiple Inheritance, and
Virtual bases: in combination. And templates and
exceptions will add to this technology.

4) Many programmers come from a Smalltalk background.
So they are still thinking of OOP in terms of
dynamics and Object Hierarchies, techniques not
suited to C++, which is an essentially a strongly typed,
static, and compiled language.

C++ OOP is so different from Smalltalk, that I have
trouble conceiving them as both OOPLs. In fact I
tend to think of C++ as a class based language,
not an object based one.

5) There are few examples. Because of 1-4, there are few
examples for other to learn from and improve on.
I hope this will soon change :-)
Use of a technique is exponential, there is the well
known 'Snowball' or 'Band Wagon' effect.

The situation is improving: already Borland, for example,
has replaced its Object based classes with the much
better BIDS classes. Although these are not yet
quite mixin classes (BC 3.1 has the mixin disabling bug),
they do use templates and MI: the precursor to
mixins.

QUESTION: Can I get around the mixin disabling bug?

ANSWER: Yes, you can just define a dummy body for your
pure virtual functions: it will never get called, but
it does make the code look messy, and it does prevent
the linker detecting when you have failed to supply
an implementation for a pure virtual (since they're
not pure virtual if you give a dummy body).

QUESTION: Can you show me an example of mixins?

ANSWER: Yes I promise! But not yet, I'm still working on one that
is non-trivial and actually usable. This is not so easy,
as my compiler has the mixin disabling bug. The compromise
between a 'Micky-Mouse' example (not convincing) and
an industrial strength one (lots of work and may have
too much detail for educational purposes) is an example
of the sort of design problem that I'm grappling with.

I'm currently working on a text screen based database.
(No, not a persistent object store, just a device independent
record manager with windows, dialog boxes etc--usable but
not sophisticated).

However, I invite those few programmers that have used
mixins to post examples.

--
;----------------------------------------------------------------------
JOHN (MAX) SKALLER, max...@extro.ucc.su.oz.au
Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;------ SCIENTIFIC AND ENGINEERING SOFTWARE ---ph: 2 799 8223 --------

Tom O Breton

unread,
Jan 29, 1993, 5:20:34 PM1/29/93
to
John (MAX):

WRT your post on mixins:

What if two derived classes want to redefine a virtual function in
different, independent ways?

Is there a decent way to do this with mixins?

I can think of two less-than-satisfactory approaches:

Derive one from the other, thus abandoning the independent funcionality.

Extend the base class, incurring the penalty of an ever-growing base
class.


An example from a project of mine:

class Base
{ ...
virtual void output_string(char *s);
...
};

class D1 : virtual public Base //Does a verbosity check
{
...
virtual void output_string(char *s)
{ if(v <= verbosity) Base::output_string(s); }
...
}

//Does a buffer-cycle for line recall
class D2: virtual public Base
{
...
virtual void output_string(char *s)
{ buffercycle(s);
Base::output_string(s);
}
...
}

Now, the functionality is pretty independent: you can use one, the
other, none, or both. If it's both you should be able to say which one
calls the other.

Is it reasonable to expect to be able to do this?

Tom


PS: With the actual project, I was able to fudge it through because I
knew they would always be used together and the verbosity check was
always done first. But to my mind my solution was not nearly as general
as it could be.


--
The Tom spreads its huge, scaly wings and soars into the wild sky...
(t...@world.std.com)

Deviasse Robert N.

unread,
Jan 30, 1993, 1:47:36 AM1/30/93
to

John MAX Skaller previously showed how to implement mixins in C++. Well actually
he showed how symmetric mixins are implemented. CLOS mixins are inherently
assymmetric. The difference between symmetric and asymmetric mixins is subtle
but important. In symmetric mixins, mixin parents are equally important. For
example, a graphic_task class is both a graphic object and a tasked object
and neither attribute is important. In asymmetric mixins one parent is more
important than the other. In essence, one parent is the "real" class while
the other is an adjective. So in a bordered_window class, the main class
is the window while the adjective class is the bordered class.

Fortunately both types of mixins can be simulated in C++. The assymetric mixins
of CLOS depend on sibling calls and the linearization of the method calling
sequence. This "sibling" call can easily be simulated in C++ by the use of
templates. Suprisingly, multiple inheritance is not needed for assymetric
mixins. Ada 9X uses this approach to create mixins since it does not have
multiple inheritance (the last time I checked).

Both symmetric and asymmetic mixins are important and neither is superior
since the model different class relationships.

Unfortunately, I don't have enough time to describe asymmetric mixins more
fully, but I think the following two examples give the general gist of how
asymmetric mixins are implemented and used.


---------------------- CUT HERE -----------------------

// Example comparing C++ asymmetric mixins with CLOS mixins

#include<iostream.h>
#include<string.h>

/**************************************************************************/
/******************** SEE ECOOP/OOPSLA '90 Proceedings ********************/
/**************************************************************************/

// (defclass Person()
// (name)
// )
//
// (defmethod display((self Person))
// (display (slot-value self 'name))
// )
//
class Person{
private:
char _name[20];
public:
Person() { _name[0]=0; }
void name(char *name_) { strcpy(_name,name_); }
void display() const { cout << _name; }
};

// (defclass Graduate_mixin()
// (degree)
// )
// (defmethod display((self Graduate_Mixin))
// (call-next-method)
// (display (slot-value self 'degree))
// )
//
template<class T>
class Graduate_mixin: public virtual T {
private:
char _degree[20];
public:
Graduate_mixin() { _degree[0]=0; }
void degree(char *degree_) { strcpy(_degree,degree_); }
void display() const { T::display(); cout << ' ' << _degree; }
};

// (defclass Graduate(Graduate_mixin Person)
// ()
// )
//

class Graduate:public virtual Graduate_mixin<Person> {};


// (defclass Dog()
// (name)
// )
//
// (defmethod display((self Dog))
// (display (slot-value self 'name))
// )
//
// (defclass Guard_Dog(Graduate_mixin Dog)
// ()
// )
//
class Dog{
private:
char _name[20];
public:
Dog() { _name[0]=0; }
void name(char *name_) { strcpy(_name,name_); }
void display() const { cout << _name; }
};

class Guard_Dog:public virtual Graduate_mixin<Dog> {};

// (defclass Doctor_mixin()
// (degree)
// )
// (defmethod display((self Doctor))
// (display "Dr. ")
// (call-next-method)
// )
//
template<class T>
class Doctor_mixin: public virtual T {
public:
Doctor_mixin() {}
void display() const { cout << "Dr. "; T::display(); }
};


// (defclass ResearchDoctor(Doctor_mixin Graduate_mixin Person)
// ()
// )

class ResearchDoctor: public virtual Doctor_mixin< Graduate_mixin<Person> > {};


void main(){
ResearchDoctor fred;
fred.name("Fred Smith"); fred.degree("Ph. D.");
fred.display(); cout << endl;

Guard_Dog fido;
fido.name("Fido"); fido.degree("Guard Dog 1st Class");
fido.display(); cout << endl;
}


---------------------- CUT HERE -----------------------

The following example shows a quick example of how asymmetric mixins might
be useful. Note how easily the mixins are combined and how "repeated
inheritance" makes sense.

---------------------- CUT HERE -----------------------

#include<stdio.h>

class Screen{
private:
char screen[20][40];
public:
Screen() { clear(); }
void drawDot(int x,int y,char dot) { screen[y][x]=dot; }

void clear() {
for(int y=0;y<20;y++)
for(int x=0;x<40;x++)
screen[y][x]='.';
}

void refresh() {
for(int y=0;y<20;y++){
for(int x=0;x<40;x++)
putchar(screen[y][x]);
putchar('\n');
}
}
};

class Rectangle {
private:
int _x0,_y0,_x1,_y1;
public:
Rectangle() {}
Rectangle(Rectangle &r) { *this=r; }
Rectangle(int x0,int y0,int x1,int y1) : _x0(x0),_y0(y0),_x1(x1),_y1(y1) {}
void expand() { _x0--,_y0--,_x1++,_y1++; }
void getEdges(int &x0,int &y0,int &x1,int &y1) const { x0=_x0,y0=_y0,x1=_x1,y1=_y1; }

virtual void drawOn(Screen &s) {
for(int x=_x0;x<_x1;x++)
for(int y=_y0;y<_y1;y++)
s.drawDot(x,y,'*');
}
};

class Picture{
private:
const char** _bitmap;
Rectangle _extent;
public:
void initialize(Rectangle &r,const char** bitmap) { _bitmap=bitmap, _extent=r; }
virtual const Rectangle &extent() const { return _extent; }

virtual void drawOn(Screen &s) {
int x0,y0,x1,y1;
_extent.getEdges(x0,y0,x1,y1);
for(int x=x0;x<x1;x++)
for(int y=y0;y<y1;y++)
s.drawDot(x,y,_bitmap[y-y0][x-x0]);
}

};

template<class T>
class Bordered_mixin:virtual public T{
private:
Rectangle _border;
public:
void initialize(Rectangle &r,const char** bitmap) {
T::initialize(r,bitmap); _border=T::extent(); _border.expand();
}

virtual void drawOn(Screen &s) {
_border.drawOn(s); // "before" method in CLOS
T::drawOn(s); // "call-next-method" in CLOS
}

virtual const Rectangle &extent() const { return _border; }
};

template<class T>
class Grayed_mixin:virtual public T{
public:
virtual void drawOn(Screen &s) {
T::drawOn(s); // "call-next-method" in CLOS

int x0,y0,x1,y1; // "after" method in CLOS
T::extent().getEdges(x0,y0,x1,y1);
for(int x=x0;x<x1;x++)
for(int y=y0;y<y1;y++)
if ((x+y)%2)
s.drawDot(x,y,' ');
}
};

const char *bitmap[]={
" CCCCCC ",
" CCCCCC ++ ++ ",
" CC ++ ++ ",
" CC ++++ ++++ ",
" CC ++++ ++++ ",
" CCCCCC ++ ++ ",
" CCCCCC ++ ++ "
};

Screen screen;

int main(){
Picture picture;
Grayed_mixin<Picture> gm_picture;
Bordered_mixin<Picture> bm_picture;
Grayed_mixin<Bordered_mixin<Picture> > gm_bm_picture;
Bordered_mixin<Grayed_mixin<Picture> > bm_gm_picture;
Bordered_mixin<Bordered_mixin<Picture> > bm_bm_picture;

picture.initialize(Rectangle(2,2,20,9),bitmap);
gm_picture.initialize(Rectangle(2,2,20,9),bitmap);
bm_picture.initialize(Rectangle(2,2,20,9),bitmap);
gm_bm_picture.initialize(Rectangle(2,2,20,9),bitmap);
bm_gm_picture.initialize(Rectangle(2,2,20,9),bitmap);
bm_bm_picture.initialize(Rectangle(2,2,20,9),bitmap);

printf("\n\npicture\n");
picture.drawOn(screen); screen.refresh(); screen.clear();

printf("\n\ngrayed picture\n");
gm_picture.drawOn(screen); screen.refresh(); screen.clear();

printf("\n\nbordered picture\n");
bm_picture.drawOn(screen); screen.refresh(); screen.clear();

printf("\n\ngrayed bordered picture\n");
gm_bm_picture.drawOn(screen); screen.refresh(); screen.clear();

printf("\n\nbordered grayed picture\n");
bm_gm_picture.drawOn(screen); screen.refresh(); screen.clear();

printf("\n\ndoubly bordered picture\n");
bm_bm_picture.drawOn(screen); screen.refresh(); screen.clear();

return 0;
}
--
/----------------------------------+------------------------------------------\
| Robert N. Deviasse |"If we have to re-invent the wheel, |
| EMAIL: g2d...@cdf.utoronto.ca | can we at least make it round this time"|
+----------------------------------+------------------------------------------/

Deviasse Robert

unread,
Jan 30, 1993, 2:31:15 AM1/30/93
to
John MAX Skaller previously showed how to implement mixins in C++. Well actual!


---------------------- CUT HERE -----------------------

#include<iostream.h>
#include<string.h>


---------------------- CUT HERE -----------------------

---------------------- CUT HERE -----------------------

#include<stdio.h>

};

Screen screen;

return 0;
}
--
/-------------------------------+---------------------------------------------\
| Robert N. Deviasse |"The things that are not easily attained, |
| EMAIL: g2d...@cdf.utoronto.ca | are the things we cherish the most." |
+-------------------------------+---------------------------------------------/

John MAX Skaller

unread,
Feb 3, 1993, 12:08:15 PM2/3/93
to
In article <C1MzE...@world.std.com> t...@world.std.com (Tom O Breton) writes:
>John (MAX):
>
>WRT your post on mixins:
>
>What if two derived classes want to redefine a virtual function in
>different, independent ways?

Not sure what you mean. It usually best not to characterise
problems in terms of their solutions.

I can clearly make two derived classes and override
a base's virtual function independently in each, indeed if
the derived classes are to each be complete objects, this
is mandatory, buit its not what you meant is it?


>
>Is there a decent way to do this with mixins?
>
>I can think of two less-than-satisfactory approaches:
>
> Derive one from the other, thus abandoning the independent funcionality.
>
> Extend the base class, incurring the penalty of an ever-growing base
> class.
>

I still dont know what the actual problem is. I agree
both 'solutions' you mention are less than satisfactory.


>
>An example from a project of mine:

Ah, this may help ...


>
>class Base
>{ ...
> virtual void output_string(char *s);

>};
>
>class D1 : virtual public Base //Does a verbosity check
>{

> virtual void output_string(char *s)
> { if(v <= verbosity) Base::output_string(s); }
>}

I think my postings would fail on this class :-)


>
>//Does a buffer-cycle for line recall
>class D2: virtual public Base
>{

> virtual void output_string(char *s)
> { buffercycle(s);
> Base::output_string(s);
> }
>}
>

>Now, the functionality is pretty independent: you can use one, the
>other, none, or both. If it's both you should be able to say which one
>calls the other.
>
>Is it reasonable to expect to be able to do this?
>

Dont understand <again>. I presume you want to do something
like this:

class Both : public D1, public D2 {};

right?

Well, thats not so good: D1::output_string dominates Base::output_string,
and D2::output_string dominates Base::output_string,
but D1::o_s does not dominate D2::o_s nor the other way around.
So any virtual call

Base *b=new Both;
b->output_string("Hello");

would be ambiguous, and thus you cant instandtiate the class at all.

So you have to provide a version of output_string in Both:

class Both : public D1, public D2 {
void output_string(char*);
};

which can be defined to do whatever you want.

>
>PS: With the actual project, I was able to fudge it through because I
>knew they would always be used together and the verbosity check was
>always done first. But to my mind my solution was not nearly as general
>as it could be.

void Both::output_string(char *s)
{
if(v<verbosity)
{
buffercycle(s);
Base::output_string(s);
}
}

There is no 'general' solution: you have to implement a particular
solution each time you create a 'Both' class. (Both1, Both2, Both3
etc).

See the ARM and C++PL, 2nd ed for discussion of how to do this
more generally. It is indeed a nasty sort of problem.
You need to define two functions in each class,
a nonvirtual one that does the work for this subobject only,
and a virtual one that calls the nonvirtual one and then
the base non-virtual ones.

Each virtual one is responsible for ordering the calls as
required.

0 new messages