Discussion:
[xplc-general] advice on GenericComponent
Pierre Phaneuf
2003-12-22 22:21:03 UTC
Permalink
After some discussion with apenwarr, I agree that it should be less
annoying to make classes using GenericComponent take constructor
parameters, so I'm trying to figure out a way to make this possible.

What do you people have to suggest?

Two of the things that I liked from the current templated shim strategy
is that I could have a constructor and initialize stuff, and that it was
pretty much as small as if I had written it by hand.

I thought of making something that you could derive from, but it is very
difficult to make it work. It has to have access to the "uuids" member,
somehow.

If anyone has a good idea, now would be a good time to talk about it!
--
Pierre Phaneuf
http://advogato.org/person/pphaneuf/
"I am denial, guilt and fear -- and I control you"
Stephane Lajoie
2003-12-22 23:39:02 UTC
Permalink
Post by Pierre Phaneuf
After some discussion with apenwarr, I agree that it should be less
annoying to make classes using GenericComponent take constructor
parameters, so I'm trying to figure out a way to make this possible.
I agree this is annoying. However, I don't see much benefit in fixing it.

There will always be a need for every component to provide a default
constructor to make factory-based creation possible. Even if we allow
other constructors, the *user* of the component will still be stuck
with a two-phase construction (default construction followed by 'init'
function call).

To make it easy for the user, the component writer needs to provide a
small wrapper class with as many constructors as needed (maybe even
*without* a default constructor if it makes sense for that component).
Once the wrapper exists, the component writer might as well use it too.
Post by Pierre Phaneuf
I thought of making something that you could derive from, but it is very
difficult to make it work. It has to have access to the "uuids" member,
somehow.
Is there any reason we can't turn this:

class Component: public IObject, public IFoo {
...
};

template<class Component>
class GenericComponent: public Component {
...
};

into this:

template<class Component>
class GenericComponent: public IObject {
...
};

class Component: public GenericComponent<Component>, public IFoo {
...
};

?

This is called the "strangely recurring template pattern" in some
circles. Since it recurs enough all over the place to warrant such a
name, maybe it would be appropriate here. Incidentally, that's how ATL
works.

I can imagine that the IObject pure virtuals from IFoo may cause pain
but I don't see any problem with the uuids member...

What an I missing?
--
Stéphane Lajoie
***@cam.org
Avery Pennarun
2003-12-23 00:08:01 UTC
Permalink
Post by Stephane Lajoie
I agree this is annoying. However, I don't see much benefit in fixing it.
There will always be a need for every component to provide a default
constructor to make factory-based creation possible.
This is untrue, because, IMHO, IFactory is evil and I'll never want to use
it. Take a look at WvMoniker in WvStreams, for example, for a way to make
your factory take parameters and simply pass them on to the constructor.

This argument is a bit circular, because I only think IFactory is evil
because it doesn't take any parameters. But the circular argument isn't
*my* fault: the justification for IFactory in the first place is circular:

- I can't allocate objects in the caller
- therefore I need an IFactory that will do it
- but IFactory's object creator doesn't take any parameters
- therefore the object it creates must have a default constructor
- therefore the user will have to call init() anyway
- therefore IFactory's object creator might as well not take any
parameters
- therefore we can define an IFactory interface that's the same for all
objects, and its object creator needn't take any parameters.

This is just silly. If IFactory::createObject() took some parameters, then
the factory could happily pass them on to the constructor of the object it
creates. What kind of parameters should it take, you ask? I propose what I
did with WvMoniker: a string, an IObject*, and a void*. That should be
general enough for "almost" anything. Anything else, okay, fine, use an
init() function.

Or ignore me entirely: since I will never want to just be able to say, "Hey,
you! Give me a new object!" instead of "Hey, you! Give me an object!",
unless I know who "you" is, I don't need IFactory at all. If I know who
"you" is, I can query him for an interface to a function that takes exactly
the parameters I want.

Death to IFactory!

Have fun,

Avery
Pierre Phaneuf
2003-12-23 00:38:02 UTC
Permalink
Post by Avery Pennarun
Or ignore me entirely: since I will never want to just be able to
say, "Hey, you! Give me a new object!" instead of "Hey, you! Give
me an object!", unless I know who "you" is, I don't need IFactory at
all. If I know who "you" is, I can query him for an interface to a
function that takes exactly the parameters I want.
Exactly.

As for the "taking parameters but still generic" IFactory, I find this
rather yucky. You might as well be specific, by that point. This is more
up the alley of IMoniker, who takes a string and is allowed to do
whatever it wants, including construct a new object and return it.

If you want to add parameters to IMoniker, this is another discussion,
but it's still allowed, as IMoniker is just another interface, if you
want to make your own similar interface, this is perfectly allowable.
This might fragment the XPLC component world a bit between those who use
one and those who use the other, but if you make your IWvMoniker derive
from IMoniker and implement the IMoniker way by assuming NULLs for the
two pointer parameters, you'd be well on your way to be compatible with
both kinds (in one direction only, but it's the "right" one for you, so
you should be happy!).
--
Pierre Phaneuf
http://advogato.org/person/pphaneuf/
"I am denial, guilt and fear -- and I control you"
Stephane Lajoie
2003-12-23 20:12:01 UTC
Permalink
Post by Avery Pennarun
Post by Stephane Lajoie
There will always be a need for every component to provide a default
constructor to make factory-based creation possible.
This is untrue, because, IMHO, IFactory is evil and I'll never want to use
it. Take a look at WvMoniker in WvStreams, for example, for a way to make
your factory take parameters and simply pass them on to the constructor.
Those parameters have to be the same (in type and amount) for every
component. This works well in WvMoniker because a "monikerable"
component is expected to be constructible from a string, but it's not
really different from IFactory + default constructor, only more flexible.

More flexibility is certainly good but the strong typing of ordinary
constructor parameters is really the biggest loss here. Note that
IDispatch is flexible and loose-typed too and nobody likes it for
exactly the same reasons...
Post by Avery Pennarun
Or ignore me entirely: since I will never want to just be able to say, "Hey,
you! Give me a new object!" instead of "Hey, you! Give me an object!",
unless I know who "you" is, I don't need IFactory at all. If I know who
"you" is, I can query him for an interface to a function that takes exactly
the parameters I want.
This is exactly what I would do most of the time also. Let's say I
have a module providing a set of GUI objects. I would get XPLC
(mostly) out of the way very early by asking for some kind of root
object, and then only use that object to create other objects (which
can also be used to create yet more objects):

xplc_ptr<IGui> gui = XPLC::create<IGui>(CID_gui);
xplc_ptr<IWindow> root = gui->createWindow("My app", ...);
xplc_ptr<IWindow> window = root->createSubwindow(0, 0, 200, 100, ...);
...

Almost all of these objects would be a specialized factory for other
related objects in the module. The xplc-cxx binding already does a
fine job of hiding IFactory and the IObject calls so this has all the
benefits of loose coupling while maintaining strong typing and looking
almost non-ugly.

The only remaining problem, in my mind, is the impossibility of doing
this (inside the module implementation):

class Gui: public IGui {
public:
virtual IWindow *createWindow(const char *title, ...) {
return new Window(title);
}
};

instead, I have to do this:

class Gui: public IGui {
public:
virtual IWindow *createWindow(const char *title, ...) {
Window *ret = new GenericComponent<Window>;
ret->init(title, ...);
return ret;
}
};

The first syntax would be possible only if the component class was
deriving from GenericComponent instead of the other way around.
(not that I know how to make this work...)
--
Stéphane Lajoie
***@cam.org
Pierre Phaneuf
2003-12-27 19:47:01 UTC
Permalink
Post by Stephane Lajoie
Those parameters have to be the same (in type and amount) for every
component. This works well in WvMoniker because a "monikerable"
component is expected to be constructible from a string, but it's not
really different from IFactory + default constructor, only more flexible.
More flexibility is certainly good but the strong typing of ordinary
constructor parameters is really the biggest loss here. Note that
IDispatch is flexible and loose-typed too and nobody likes it for
exactly the same reasons...
That's what I don't like about the WvMoniker idea, I like just the
string... Just a string is good, because you can do stuff with it, like
put it in config file, send it over the network or even have a user type
it in without his head exploding (it isn't very nice, but you can live
with it, witness all these URIs people are getting thrown all the time).

When you start putting more, like a random IObject pointer (which is
only a hair better than a void*, in this context) and a void* (speaking
of the devil...), you open the door to all sorts of funny things and
context-dependent failures. You can't put this particular moniker string
in a config file anymore, because it doesn't return an object if you
don't pass it an object of exactly the right interface, as well as a
some other piece junk in the void*.

But the way XPLC is designed is to allow apenwarr to fulfill all his
crazy void*-filled fantasies without obstruction, so it will. We're also
all free to use it or not, which is, in the end, what is really important.
Post by Stephane Lajoie
This is exactly what I would do most of the time also. Let's say I have
a module providing a set of GUI objects. I would get XPLC (mostly) out
of the way very early by asking for some kind of root object, and then
only use that object to create other objects (which can also be used to
xplc_ptr<IGui> gui = XPLC::create<IGui>(CID_gui);
xplc_ptr<IWindow> root = gui->createWindow("My app", ...);
xplc_ptr<IWindow> window = root->createSubwindow(0, 0, 200, 100, ...);
...
Almost all of these objects would be a specialized factory for other
related objects in the module. The xplc-cxx binding already does a fine
job of hiding IFactory and the IObject calls so this has all the
benefits of loose coupling while maintaining strong typing and looking
almost non-ugly.
This is just about the exact way I envisioned it. I don't want XPLC to
be a burden, and while you can certainly let it be a burden, you have to
want it to. The rules on interfaces are the only truly important rules,
how you stumbled on a pointer to that interface, that's a mere detail.

Note that Stéphane's example would have excellent ABI compatbility
caracteristics. He could create a IGui2 interface, rename the old IGui
to IGui1 (without changing it's methods, their meaning or its UUID),
have a "#define IGui IGui2" (for nicety), and one could either use the
IGui of the day through the typedef or if he doesn't use any feature of
the newer version, use IGui1 (maybe redefine IGui, for nicety again) so
that people that only have the older version can still use his software,
without requiring him to actually have the older software (as with glibc
or Qt, for example).

IGui2 could be derived from IGui1, if only a few methods were added
(like "createFunkyWindow"), which would allow software to directly pass
in IGui2 where IGui1 is wanted, in exchange for not being able to change
or remove methods, only adding them. Or IGui2 could *not* derive from
IGui1, but its implementation's getInterface could give a translation
wrapper when asked for IGui1. Or you could just send IGui1 to rot in the
pits of hell.

The point is, you *have the choice*. With C or C++, you hardly have any
choice at all, and there's usually no compatibility at all between two
versions (if you have software linked against Qt 3.0.3, it cannot use a
library containing a few extra widgets linked against Qt 2, or possibly
even Qt 3.0.1!).
Post by Stephane Lajoie
The only remaining problem, in my mind, is the impossibility of doing
class Gui: public IGui {
virtual IWindow *createWindow(const char *title, ...) {
return new Window(title);
}
};
class Gui: public IGui {
virtual IWindow *createWindow(const char *title, ...) {
Window *ret = new GenericComponent<Window>;
ret->init(title, ...);
return ret;
}
};
The first syntax would be possible only if the component class was
deriving from GenericComponent instead of the other way around.
(not that I know how to make this work...)
Well, I've shown a way. This is a "mere" implementation detail, there's
nothing in XPLC's design that says that GenericComponent should even
exist, only its goal of ease of use. And the fact that you have to have
the two-phase init in a method like createWindow is rather silly (if you
made a C++ class that wouldn't have a default constructor, why should
you do so with XPLC?).

There's a few cool things that you get if you provide a default
constructor (particularly in a dynamic system like XPLC), but if you
don't need them, you don't need the default constructor either!
--
Pierre Phaneuf
http://advogato.org/person/pphaneuf/
"I am denial, guilt and fear -- and I control you"
Avery Pennarun
2003-12-28 19:30:16 UTC
Permalink
Post by Pierre Phaneuf
That's what I don't like about the WvMoniker idea, I like just the
string... Just a string is good, because you can do stuff with it, like
put it in config file, send it over the network or even have a user type
it in without his head exploding (it isn't very nice, but you can live
with it, witness all these URIs people are getting thrown all the time).
But you miss a key bit of flexibility: passing an object you just obtained
from elsewhere to a user-defined moniker. For example, when I make a
generic WvListener (and a WvTcpListener based on it), the listener will know
how to create WvTcpStreams automatically. But I'll want to wrap it in
something user-defined (yes, maybe even in a config file, despite your
claims), such as "wvdial:" or "uniconfprotocol:ssl:". These are the exact
same monikers I might want to use to construct streams from scratch, if
there's no pre-existing IObject*:

wvdial:modem:/dev/ttyS0
uniconfprotocol:ssl:tcp:localhost:80

That IObject* is exactly what I need for this case, and I don't see any
way of doing it without at least one of:

- a "pointer" moniker that takes a hexified pointer to IObject

- a special nonstandard interface, hereby nicknamed "WvMoniker", that does
what I want

- registering each kind of wrapper in more than one moniker registry

- ...more, even stupider things...

The typesafety argument is a red herring. IObject* is perfectly type safe,
albeit at run time. If you want compile-time safety, you'll *have* to
define your own interfaces anyway, and there's nothing stopping you from
doing that.

Furthermore, someone who doesn't care about the IObject* doesn't really pay
anything at all, so other than your theory that "you probably don't need it"
(which I hope I've disproven, since I need it), I still haven't seen an
argument against providing it.

I *already* use this feature of WvMoniker, so YAGNI isn't a question here.
Leaving it out *prevents* me from elegantly mapping WvMoniker onto IMoniker,
which fragments the users of your moniker interface before you even have
more than one. Why?

Have fun,

Avery
Stephane Lajoie
2003-12-29 19:24:29 UTC
Permalink
Post by Pierre Phaneuf
Well, I've shown a way. This is a "mere" implementation detail, there's
nothing in XPLC's design that says that GenericComponent should even
exist, only its goal of ease of use. And the fact that you have to have
the two-phase init in a method like createWindow is rather silly (if you
made a C++ class that wouldn't have a default constructor, why should
you do so with XPLC?).
I would be "forced" to do so because of the way GenericComponent
works. Of course I'm not really forced to use GenericComponent (I
could implement everything by hand for every object, or roll out my
own GenericComponent that doesn't impose a construction interface) but
I'd much rather use the same thing everybody else is using.

In my opinion, the ease of use goal is a *lot* more important than the
size of xplc.so or any given module or test program. That's just me
though: I don't balk at a 21MB runtime module (the .NET Framework)
when it gives me what I want, so feel free to take this with a grain
of salt :).
--
Stéphane Lajoie
***@cam.org
Avery Pennarun
2003-12-29 19:39:06 UTC
Permalink
Post by Stephane Lajoie
In my opinion, the ease of use goal is a *lot* more important than the
size of xplc.so or any given module or test program. That's just me
though: I don't balk at a 21MB runtime module (the .NET Framework)
when it gives me what I want, so feel free to take this with a grain
of salt :).
Nowadays, your size has to change by a fairly large number of *kilobytes*
before anybody cares too much. And it's a percentage thing; if one class
changes by a few bytes, I don't care, and if 1000 classes change by a total
of a few bytes each, I still don't care :)

I appreciate the goal of XPLC to be small, and wherever you can be small
*and* easy to use *and* simple, that's what you should do. But if it comes
down to it, which will affect XPLC's adoption rate more?

- it's inconvenient to map your existing objects into it

- it wastes 1k per object type.

I know which one *I* care about more... and *I* build operating systems that
are supposed to all fit in 16 megs :)

Have fun,

Avery
Pierre Phaneuf
2003-12-31 16:28:02 UTC
Permalink
Post by Avery Pennarun
Nowadays, your size has to change by a fairly large number of
*kilobytes* before anybody cares too much. And it's a percentage
thing; if one class changes by a few bytes, I don't care, and if 1000
classes change by a total of a few bytes each, I still don't care :)
Agreed, although I find this fun, and my level of personal fun is very
important in XPLC development, as it makes me work on it more on other
things too! ;-)
Post by Avery Pennarun
I appreciate the goal of XPLC to be small, and wherever you can be
small *and* easy to use *and* simple, that's what you should do. But
if it comes down to it, which will affect XPLC's adoption rate more?
- it's inconvenient to map your existing objects into it
- it wastes 1k per object type.
I know which one *I* care about more... and *I* build operating
systems that are supposed to all fit in 16 megs :)
Except in the "vptr bloat" I'm talking about, we're not talking about
wasting memory per object *type*, but rather per object *instance*. It's
at 12 bytes at the moment, which isn't so bad, but it's not the kind of
thing you want to see inflate at a hundred bytes or so!

Now, my latest change main goal was *not* at all memory saving (which
just happened as a lucky side-effect!), it was to make it easy (*more*
convenient!) to have objects with non-default constructors. It was
already possible, with the base class trick (you make an empty base
class FooBase that derives from the appropriate interfaces, then derive
Foo from GenericComponent<FooBase>), but it is now easier. Now, it just
looks like this:

class Foo: public IFoo, public IBar {
IMPLEMENT_IOBJECT(Foo);
public:
Foo(int something, char* otherstuff);
...
};

And you can then just go 'new Foo(42, "blah")'. You still have the
interface map though, but it works exactly the same, no change needed.

I also made the refcount default to 1, because just about everywhere,
"new" was immediately followed by a addRef(), so by doing so, I reduced
both the line count (increasing clarity and decreasing typing!) and the
code size. Less code size is good, but less typing is even more
important! Getting both is just creamy goodness... :-)
--
Pierre Phaneuf
http://advogato.org/person/pphaneuf/
"I am denial, guilt and fear -- and I control you"
Avery Pennarun
2003-12-31 16:51:05 UTC
Permalink
Post by Pierre Phaneuf
Except in the "vptr bloat" I'm talking about, we're not talking about
wasting memory per object *type*, but rather per object *instance*. It's
at 12 bytes at the moment, which isn't so bad, but it's not the kind of
thing you want to see inflate at a hundred bytes or so!
Yeah, I was thinking more about code size, ie. an extra virtual function
(IObject member) that you might have to implement in IMPLEMENT_IOBJECT.
That would have been a few bytes per class, not per instance. Obviously I'd
be angry if a very common object suddenly bloated up every single instance
just because it uses XPLC.

In fairness, though, *really* small objects that don't do much don't have to
use XPLC at all, and can still be returned by XPLC-compliant objects.
WvString, for example, is only 8 bytes, so the existing XPLC overhead is
already too much. But my XPLC-enabled objects can still return WvStrings
(with all the usual limitations).
Post by Pierre Phaneuf
class Foo: public IFoo, public IBar {
IMPLEMENT_IOBJECT(Foo);
Okay, that will probably not kill me. Now, can I still do this?

class Blue : public Foo, public IWoz {
IMPLEMENT_IOBJECT(Blue);
...
};

Have fun,

Avery
Pierre Phaneuf
2003-12-31 17:33:02 UTC
Permalink
Post by Avery Pennarun
Post by Pierre Phaneuf
Except in the "vptr bloat" I'm talking about, we're not talking
about wasting memory per object *type*, but rather per object
*instance*. It's at 12 bytes at the moment, which isn't so bad, but
it's not the kind of thing you want to see inflate at a hundred
bytes or so!
Yeah, I was thinking more about code size, ie. an extra virtual
function (IObject member) that you might have to implement in
IMPLEMENT_IOBJECT. That would have been a few bytes per class, not
per instance. Obviously I'd be angry if a very common object
suddenly bloated up every single instance just because it uses XPLC.
Yeah, a few bytes per class is not big deal, that's for sure!
Post by Avery Pennarun
In fairness, though, *really* small objects that don't do much don't
have to use XPLC at all, and can still be returned by XPLC-compliant
objects. WvString, for example, is only 8 bytes, so the existing XPLC
overhead is already too much. But my XPLC-enabled objects can still
return WvStrings (with all the usual limitations).
Yes, exactly.
Post by Avery Pennarun
Post by Pierre Phaneuf
class Foo: public IFoo, public IBar {
IMPLEMENT_IOBJECT(Foo);
Okay, that will probably not kill me. Now, can I still do this?
class Blue : public Foo, public IWoz {
IMPLEMENT_IOBJECT(Blue);
...
};
Yeah, you can. Remember that the interface map for "Blue" has to contain
*all* the interfaces that it supports, including those of "Foo", but
this was already the case before (I'm just reminding you!).

I could add an UUID_MAP_BEGIN_DERIVED that would allow you to "chain"
interface maps and add just what you need, like this:

UUID_MAP_BEGIN_DERIVED(Blue, Foo)
UUID_MAP_ENTRY(IWoz)
UUID_MAP_END

I'm not 100% sure this this possible, but I think it might. On the other
hand, I don't think this is very important and wouldn't make much of a
difference... Could always be done later.
--
Pierre Phaneuf
http://advogato.org/person/pphaneuf/
"I am denial, guilt and fear -- and I control you"
Avery Pennarun
2003-12-31 17:43:02 UTC
Permalink
Post by Pierre Phaneuf
I could add an UUID_MAP_BEGIN_DERIVED that would allow you to "chain"
UUID_MAP_BEGIN_DERIVED(Blue, Foo)
UUID_MAP_ENTRY(IWoz)
UUID_MAP_END
I'm not 100% sure this this possible, but I think it might. On the other
hand, I don't think this is very important and wouldn't make much of a
difference... Could always be done later.
Yeah, I don't think we really need that. It could conceivably be useful to
explicitly *not* list certain interfaces in the derived class, after all.

Have fun,

Avery

Pierre Phaneuf
2003-12-31 16:14:10 UTC
Permalink
Post by Stephane Lajoie
Post by Pierre Phaneuf
Well, I've shown a way. This is a "mere" implementation detail,
there's nothing in XPLC's design that says that GenericComponent
should even exist, only its goal of ease of use. And the fact that
you have to have the two-phase init in a method like createWindow
is rather silly (if you made a C++ class that wouldn't have a
default constructor, why should you do so with XPLC?).
I would be "forced" to do so because of the way GenericComponent
works. Of course I'm not really forced to use GenericComponent (I
could implement everything by hand for every object, or roll out my
own GenericComponent that doesn't impose a construction interface)
but I'd much rather use the same thing everybody else is using.
That's why I'm trying to improve or replace GenericComponent. I
understand that most people would rather use the same code as everyone
else, for consistency's and not reinventing-the-wheel's sake, so this is
a good reason for making the bundled-in helper code be as good as possible.

If it isn't, we might easily have a "bad, but good enough" phenomenon
with regard to this, where people would hate it, but wouldn't care
enough to make another one because it would still be "usable enough".
Post by Stephane Lajoie
In my opinion, the ease of use goal is a *lot* more important than
the size of xplc.so or any given module or test program. That's just
me though: I don't balk at a 21MB runtime module (the .NET Framework)
when it gives me what I want, so feel free to take this with a grain
of salt :).
Oh, yes, of course! One of the goals for XPLC though is for me to have
fun, and I like optimizing things like the size, if that's what I'd
rather do. :-)

That said, when I refer to "vtable bloat", I'm actually thinking of
"vptr bloat" (sorry for confusing everyone!). This is what happens when
you use multiple inheritance with many classes that have vtables (those
that have virtual methods). Each multiple inheritance adds a vptr
(virtual method table pointer) in each and every instances.

The minimal cost of being an XPLC object is already of 12 bytes per
instance (at least one vptr, the refcount and the weak ref pointer).
Adding *another* 4 bytes to every single instance isn't very good, IMHO,
and is starting to be rather costly for something that claims to be
lightweight, thin and otherwise invisible.

Optimizing the static size of the libxplc.so isn't that important, other
than generally not looking awful (I wouldn't want it to be hundreds of
kilobytes either!) and entertaining me late at night. The overhead for a
every single instance is another thing. It adds up quickly in a large
program, just look at Mozilla, which isn't as careful and has lots of
objects at any one time. Having lots of objects is allowed, but they
have inter-thread proxying gunk that goes up with the object count and
hurts pretty bad.
--
Pierre Phaneuf
http://advogato.org/person/pphaneuf/
"I am denial, guilt and fear -- and I control you"
Pierre Phaneuf
2003-12-23 00:31:02 UTC
Permalink
Post by Stephane Lajoie
Post by Pierre Phaneuf
After some discussion with apenwarr, I agree that it should be less
annoying to make classes using GenericComponent take constructor
parameters, so I'm trying to figure out a way to make this
possible.
I agree this is annoying. However, I don't see much benefit in fixing it.
There will always be a need for every component to provide a default
constructor to make factory-based creation possible. Even if we
allow other constructors, the *user* of the component will still be
stuck with a two-phase construction (default construction followed by
'init' function call).
That was part of apenwarr's critic. His point was that nothing at this
moment *forces* you to actually use the IFactory interface for anything.
I makes some things easier (like allows you to be instantiated through
the "new:" moniker), but it's just a convention. For example, the module
loader: instead of having a IModuleLoader::setDirectory method, there
could be a specific IModuleLoaderFactory that you'd get when you want to
create a module loader and its "create" method would take the directory
as its parameter.

The IFactory interface probably shouldn't go away, and any component
that has a default constructor should have its factory inherit from
IFactory so that it can still be created this way.
Post by Stephane Lajoie
This is called the "strangely recurring template pattern" in some
circles. Since it recurs enough all over the place to warrant such a
name, maybe it would be appropriate here. Incidentally, that's how
ATL works.
Hmm, I'd be curious to see how the equivalent ATL template works... Can
I find this somewhere on the web (maybe tell me the name of the
template)? Link to MSDN?

I just made a monster macro (and a not-so-monster macro to go with it)
that implements IObject, like this:

class Component: IFoo {
IMPLEMENT_IOBJECT(Component);
public:
Component(): INIT_IOBJECT() {
}
...
};

You have to put the IMPLEMENT_IOBJECT first, and your constructors (no
matter how many parameters!) have to have INIT_IOBJECT() at the
beginning of their initializer list.

Now, I find this rather ugly looking, particularly the INIT_IOBJECT()
part (it initializes the member variables introduced by
IMPLEMENT_IOBJECT), but I have to admit that this shrank libxplc.so by
1564 bytes, testmain by 2485 bytes and testobj.dll by 480 bytes (4529
bytes total), which is having me foaming at the mouth.

I think what hurts the most is that this looks a lot like COM, and this
is rather painful for me to think about!
Post by Stephane Lajoie
I can imagine that the IObject pure virtuals from IFoo may cause pain
but I don't see any problem with the uuids member...
If I recall correctly, the compiler whines about the pure virtuals, even
though it just gained implementations for them from the other parent.
That was in fact one of my first attempt at automating IObject, a mix-in
class.

As a note, even if that'd work, you'd need to have GenericComponent
derive from an interface, to avoid vptr bloat (deriving from just
IObject is useless). Like this:

template<class Component, Interface>
class GenericComponent: public Interface {
...
};

class Component: public GenericComponent<Component, IFoo> {
...
};

But it doesn't work anyway... Unless ATL has something smart to propose!

I had an alternative that involved making the GenericComponent template
take a variable number of interfaces and derive from them all. But this
is just pretty damn yucky...
--
Pierre Phaneuf
http://advogato.org/person/pphaneuf/
"I am denial, guilt and fear -- and I control you"
Pierre Phaneuf
2003-12-23 01:51:01 UTC
Permalink
Post by Pierre Phaneuf
But it doesn't work anyway... Unless ATL has something smart to propose!
I had a look (at
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/_atl_implementing_ccomobject.2c_.ccomaggobject.2c_.and_ccompolyobject.asp),
and it seems that ATL both uses a bunch of templates and classes that
you derive from *and* a final shim template (like GenericComponent) to
tie it all together (that's where the IUnknown methods are really
implemented, although as calls to parent class methods).

Maybe I can arrive at a compromise...
--
Pierre Phaneuf
http://advogato.org/person/pphaneuf/
"I am denial, guilt and fear -- and I control you"
Stephane Lajoie
2003-12-23 20:58:02 UTC
Permalink
Post by Pierre Phaneuf
I just made a monster macro (and a not-so-monster macro to go with it)
class Component: IFoo {
IMPLEMENT_IOBJECT(Component);
Component(): INIT_IOBJECT() {
}
...
};
You have to put the IMPLEMENT_IOBJECT first, and your constructors (no
matter how many parameters!) have to have INIT_IOBJECT() at the
beginning of their initializer list.
Now, I find this rather ugly looking, particularly the INIT_IOBJECT()
part (it initializes the member variables introduced by
IMPLEMENT_IOBJECT), but I have to admit that this shrank libxplc.so by
1564 bytes, testmain by 2485 bytes and testobj.dll by 480 bytes (4529
bytes total), which is having me foaming at the mouth.
At the risk of triggering more foaming, I can tell you how to get rid
of INIT_IOBJECT:
template<class T>
class ZeroInitialized {
public:
ZeroInitialized(): val(0) { }
ZeroInitialized(const T &val): val(val) { }

operator T() const { return val; }
/* plus any other operators used by IMPLEMENT_IOBJECT; 'tis
a shame we can't derive from basic types since that would
make this so much easier */

private:
T val;
};

then use ZeroInitialized<int> and ZeroInitialized<IWeakRef *> in
IMPLEMENT_IOBJECT. Or maybe that should be xplc_ptr<IWeakRef>,
considering the apparent memory leak in GenericComponent which doesn't
seem to release the associated weakref object (you know, having no
destructor and all...).
Post by Pierre Phaneuf
I think what hurts the most is that this looks a lot like COM, and this
is rather painful for me to think about!
Nah, COM has __ATL_NO_VTABLE, FinalConstruct and FinalRelease,
DECLARE_NOT_AGGREGATABLE, DECLARE_PROTECT_FINAL_CONSTRUCT and so many
more. You could spend your entire life trying to make xplc ugly and
still not come close to COM's ugliness.
Post by Pierre Phaneuf
Post by Stephane Lajoie
I can imagine that the IObject pure virtuals from IFoo may cause pain
but I don't see any problem with the uuids member...
If I recall correctly, the compiler whines about the pure virtuals, even
though it just gained implementations for them from the other parent.
That's what I thought. Virtual derivation would fix that but that's
likely a can of worm we don't want to open...
Post by Pierre Phaneuf
I had an alternative that involved making the GenericComponent template
take a variable number of interfaces and derive from them all. But this
is just pretty damn yucky...
Actually I don't think that's so bad, assuming it can be made to work.

Alexandrescu has stuff about combining a typelist of any length into a
single derived type in his crack book. However, if I remember
correctly this would likely produce what you call vtable bloat...
--
Stéphane Lajoie
***@cam.org
Pierre Phaneuf
2003-12-27 21:26:01 UTC
Permalink
At the risk of triggering more foaming, I can tell you how to get rid of
template<class T>
class ZeroInitialized {
ZeroInitialized(): val(0) { }
ZeroInitialized(const T &val): val(val) { }
operator T() const { return val; }
/* plus any other operators used by IMPLEMENT_IOBJECT; 'tis
a shame we can't derive from basic types since that would
make this so much easier */
T val;
};
then use ZeroInitialized<int> and ZeroInitialized<IWeakRef *> in
IMPLEMENT_IOBJECT. Or maybe that should be xplc_ptr<IWeakRef>,
considering the apparent memory leak in GenericComponent which doesn't
seem to release the associated weakref object (you know, having no
destructor and all...).
I had something like this:

struct GenericComponentInternal {
unsigned int refcount;
IWeakRef* weakref;
GenericComponentInternal(): refcount(1), weakref(0) {}
};

I put an instance of this in GenericComponent, but if I recall
correctly, it was a bit bigger, but I don't remind relative to what (did
I put this in the GenericComponent template or in the macro?). If I put
it in the template, it's very easy to picture it being bigger than the
original template, but if that were the case, maybe putting it in the
macro, while making it bigger than the macro I have now, might be still
smaller than the current template. That would be a good win!
Post by Pierre Phaneuf
I think what hurts the most is that this looks a lot like COM, and
this is rather painful for me to think about!
Nah, COM has __ATL_NO_VTABLE, FinalConstruct and FinalRelease,
DECLARE_NOT_AGGREGATABLE, DECLARE_PROTECT_FINAL_CONSTRUCT and so many
more. You could spend your entire life trying to make xplc ugly and
still not come close to COM's ugliness.
That's comforting to hear, but I'd rather veer on the safe side! :-)
Post by Pierre Phaneuf
I had an alternative that involved making the GenericComponent
template take a variable number of interfaces and derive from them
all. But this is just pretty damn yucky...
Actually I don't think that's so bad, assuming it can be made to work.
Well, apenwarr's desk is just beside mine, he could too easily set up
some kind of deadly boobytrap... ;-)
Alexandrescu has stuff about combining a typelist of any length into
a single derived type in his crack book. However, if I remember
correctly this would likely produce what you call vtable bloat...
The output of "size -t libxplc.so tests/testmain tests/testobj.dll" is
my guide. If the number is smaller, then so be it. I also like more text
than data or bss (means more is shared between processes and also that
less is initialized at runtime).

Reminder: I heard silliness recently that the text segment contained
only the code. That is untrue. It contains what is read-only. That's why
I like global or static const data!
--
Pierre Phaneuf
http://advogato.org/person/pphaneuf/
"I am denial, guilt and fear -- and I control you"
Pierre Phaneuf
2003-12-27 22:50:01 UTC
Permalink
At the risk of triggering more foaming, I can tell you how to get rid of
template<class T>
class ZeroInitialized {
ZeroInitialized(): val(0) { }
ZeroInitialized(const T &val): val(val) { }
operator T() const { return val; }
/* plus any other operators used by IMPLEMENT_IOBJECT; 'tis
a shame we can't derive from basic types since that would
make this so much easier */
T val;
};
then use ZeroInitialized<int> and ZeroInitialized<IWeakRef *> in
IMPLEMENT_IOBJECT. Or maybe that should be xplc_ptr<IWeakRef>,
considering the apparent memory leak in GenericComponent which doesn't
seem to release the associated weakref object (you know, having no
destructor and all...).
I had something like this:

struct GenericComponentInternal {
unsigned int refcount;
IWeakRef* weakref;
GenericComponentInternal(): refcount(1), weakref(0) {}
};

I put an instance of this in GenericComponent, but if I recall
correctly, it was a bit bigger, but I don't remind relative to what (did
I put this in the GenericComponent template or in the macro?). If I put
it in the template, it's very easy to picture it being bigger than the
original template, but if that were the case, maybe putting it in the
macro, while making it bigger than the macro I have now, might be still
smaller than the current template. That would be a good win!
Post by Pierre Phaneuf
I think what hurts the most is that this looks a lot like COM, and
this is rather painful for me to think about!
Nah, COM has __ATL_NO_VTABLE, FinalConstruct and FinalRelease,
DECLARE_NOT_AGGREGATABLE, DECLARE_PROTECT_FINAL_CONSTRUCT and so many
more. You could spend your entire life trying to make xplc ugly and
still not come close to COM's ugliness.
That's comforting to hear, but I'd rather veer on the safe side! :-)
Post by Pierre Phaneuf
I had an alternative that involved making the GenericComponent
template take a variable number of interfaces and derive from them
all. But this is just pretty damn yucky...
Actually I don't think that's so bad, assuming it can be made to work.
Well, apenwarr's desk is just beside mine, he could too easily set up
some kind of deadly boobytrap... ;-)
Alexandrescu has stuff about combining a typelist of any length into
a single derived type in his crack book. However, if I remember
correctly this would likely produce what you call vtable bloat...
The output of "size -t libxplc.so tests/testmain tests/testobj.dll" is
my guide. If the number is smaller, then so be it. I also like more text
than data or bss (means more is shared between processes and also that
less is initialized at runtime).

Reminder: I heard silliness recently that the text segment contained
only the code. That is untrue. It contains what is read-only. That's why
I like global or static const data!
--
Pierre Phaneuf
http://advogato.org/person/pphaneuf/
"I am denial, guilt and fear -- and I control you"
Pierre Phaneuf
2003-12-28 03:00:01 UTC
Permalink
At the risk of triggering more foaming, I can tell you how to get rid of
template<class T>
class ZeroInitialized {
ZeroInitialized(): val(0) { }
ZeroInitialized(const T &val): val(val) { }
operator T() const { return val; }
/* plus any other operators used by IMPLEMENT_IOBJECT; 'tis
a shame we can't derive from basic types since that would
make this so much easier */
T val;
};
then use ZeroInitialized<int> and ZeroInitialized<IWeakRef *> in
IMPLEMENT_IOBJECT. Or maybe that should be xplc_ptr<IWeakRef>,
considering the apparent memory leak in GenericComponent which doesn't
seem to release the associated weakref object (you know, having no
destructor and all...).
I had something like this:

struct GenericComponentInternal {
unsigned int refcount;
IWeakRef* weakref;
GenericComponentInternal(): refcount(1), weakref(0) {}
};

I put an instance of this in GenericComponent, but if I recall
correctly, it was a bit bigger, but I don't remind relative to what (did
I put this in the GenericComponent template or in the macro?). If I put
it in the template, it's very easy to picture it being bigger than the
original template, but if that were the case, maybe putting it in the
macro, while making it bigger than the macro I have now, might be still
smaller than the current template. That would be a good win!
Post by Pierre Phaneuf
I think what hurts the most is that this looks a lot like COM, and
this is rather painful for me to think about!
Nah, COM has __ATL_NO_VTABLE, FinalConstruct and FinalRelease,
DECLARE_NOT_AGGREGATABLE, DECLARE_PROTECT_FINAL_CONSTRUCT and so many
more. You could spend your entire life trying to make xplc ugly and
still not come close to COM's ugliness.
That's comforting to hear, but I'd rather veer on the safe side! :-)
Post by Pierre Phaneuf
I had an alternative that involved making the GenericComponent
template take a variable number of interfaces and derive from them
all. But this is just pretty damn yucky...
Actually I don't think that's so bad, assuming it can be made to work.
Well, apenwarr's desk is just beside mine, he could too easily set up
some kind of deadly boobytrap... ;-)
Alexandrescu has stuff about combining a typelist of any length into
a single derived type in his crack book. However, if I remember
correctly this would likely produce what you call vtable bloat...
The output of "size -t libxplc.so tests/testmain tests/testobj.dll" is
my guide. If the number is smaller, then so be it. I also like more text
than data or bss (means more is shared between processes and also that
less is initialized at runtime).

Reminder: I heard silliness recently that the text segment contained
only the code. That is untrue. It contains what is read-only. That's why
I like global or static const data!
--
Pierre Phaneuf
http://advogato.org/person/pphaneuf/
"I am denial, guilt and fear -- and I control you"
Pierre Phaneuf
2003-12-28 04:11:03 UTC
Permalink
At the risk of triggering more foaming, I can tell you how to get rid of
template<class T>
class ZeroInitialized {
ZeroInitialized(): val(0) { }
ZeroInitialized(const T &val): val(val) { }
operator T() const { return val; }
/* plus any other operators used by IMPLEMENT_IOBJECT; 'tis
a shame we can't derive from basic types since that would
make this so much easier */
T val;
};
then use ZeroInitialized<int> and ZeroInitialized<IWeakRef *> in
IMPLEMENT_IOBJECT. Or maybe that should be xplc_ptr<IWeakRef>,
considering the apparent memory leak in GenericComponent which doesn't
seem to release the associated weakref object (you know, having no
destructor and all...).
I had something like this:

struct GenericComponentInternal {
unsigned int refcount;
IWeakRef* weakref;
GenericComponentInternal(): refcount(1), weakref(0) {}
};

I put an instance of this in GenericComponent, but if I recall
correctly, it was a bit bigger, but I don't remind relative to what (did
I put this in the GenericComponent template or in the macro?). If I put
it in the template, it's very easy to picture it being bigger than the
original template, but if that were the case, maybe putting it in the
macro, while making it bigger than the macro I have now, might be still
smaller than the current template. That would be a good win!
Post by Pierre Phaneuf
I think what hurts the most is that this looks a lot like COM, and
this is rather painful for me to think about!
Nah, COM has __ATL_NO_VTABLE, FinalConstruct and FinalRelease,
DECLARE_NOT_AGGREGATABLE, DECLARE_PROTECT_FINAL_CONSTRUCT and so many
more. You could spend your entire life trying to make xplc ugly and
still not come close to COM's ugliness.
That's comforting to hear, but I'd rather veer on the safe side! :-)
Post by Pierre Phaneuf
I had an alternative that involved making the GenericComponent
template take a variable number of interfaces and derive from them
all. But this is just pretty damn yucky...
Actually I don't think that's so bad, assuming it can be made to work.
Well, apenwarr's desk is just beside mine, he could too easily set up
some kind of deadly boobytrap... ;-)
Alexandrescu has stuff about combining a typelist of any length into
a single derived type in his crack book. However, if I remember
correctly this would likely produce what you call vtable bloat...
The output of "size -t libxplc.so tests/testmain tests/testobj.dll" is
my guide. If the number is smaller, then so be it. I also like more text
than data or bss (means more is shared between processes and also that
less is initialized at runtime).

Reminder: I heard silliness recently that the text segment contained
only the code. That is untrue. It contains what is read-only. That's why
I like global or static const data!
--
Pierre Phaneuf
http://advogato.org/person/pphaneuf/
"I am denial, guilt and fear -- and I control you"
Loading...