Boost logo

Boost :

From: Alberto Ganesh Barbati (AlbertoBarbati_at_[hidden])
Date: 2008-02-03 06:38:17


JOAQUIN LOPEZ MU?Z ha scritto:
>
> I'm glad you've raised an important issue here. As it happens,
> during the design of the library I devoted some thinking to the
> map-vs-set problem (if you allow me to call it that way) and I opted
> for the current design for the following reasons:
>
> <snip>
>
> 2. Indeed GoF introduces a key type K into the pattern that
> is used to retrieve the actual values of T. So, we have
> a one-to-one relation K-->T, i.e. there exists a stateless
> function f of the form
>
> T f(const K&);
>
> that can be used to construct a T from a given K. And,
> additionally, K is cheaper to construct than T. This is
> the map approach, right? My question now is: is this a realistic
> scenario? If K is actually cheaper to construct than T and
> we can univocally get the associated T from any K, why work
> with Ts and not just use Ks in the first place? The only
> plaussible justifications I can think of is that f() is
> computationally expensive or that T is more convenient to
> work with than K, but these seem (to me) not so likely
> concerns --more on the second concern on point 3 below, though.
> Note that I explicitly observed that f must be *stateless*, i.e.
> a K object contains exactly the same information as its associated
> T value. This is not the case in most usages of std::map, which
> is a reason why std::maps are useful :)
> I am not plainly denying the existence of sensible K-->T
> scenarios, but I thought long and hard and couldn't find
> any. If you can come up with one I'll be happy to know.
> So, my analysis led me to conclude that the right approach
> is to assume that K==T, that is, the set approach, or at
> most than K and T are just different representations of the
> same information.

One such scenario is right there in the GoF book, the word-processing
application that uses one flyweight object for each glyph in the document.

I have another case in an project I am working on: in a 3D application,
I have heavyweight 3D mesh objects that might be shared among several
actors. The key of a 3D mesh is just its filename. I don't want to load
an entire mesh into memory just to find out that I have it already. Yes,
I could delay the actual loading of the mesh until the first time it is
actually used, but that would be impractical for at least two reasons:
1) any error encountered while loading the mesh would occur in the wrong
place and couldn't be handled properly, 2) the place where the mesh is
used is inside a tight rendering loop with strict real-time requirements
which can't be blocked by costly I/O operations.

>
> 3. That said, there is a natural evolution for the library that
> would eliminate T construction under some circumstances:
> C++0x containers will provide the emplace() member function allowing
> for in-place construction of an inserted elements given the
> ctor arguments, so eliminating the need to construct a
> temporary to pass to insert() and related memfuns. When this is
> available, the flyweight lib will take advantage of it, so that
>
> fw s2("foo");
>
> will create no temporary std::strings.

Actually it will always create one temporary. If the string is not
already present in the factory, the temporary is then moved into the
factory and so it is not created in vain. However, in the most common
case, the string is already present in the factor and the temporary
needs to be discarded. The main problem is that in order to avoid the
creation of the temporary you would need to be able to compare keys with
objects directly, while the current design requires constructing an
object in order to compare it with the existing objects. In this sense,
I believe the find/insert approach might help addressing the issue.

> will create no temporary std::strings. In the terminology of
> point 2, we have K==const char*, T==std::string. You can argue
> that this seems to contradict the thesis of point 2 (there
> are no sensible usa cases where K!=T), but note that this
> situation (constructing a std::string from a const char*) is
> only marginally useful: in a real program most std::strings do
> not get constructed from compile time literals, but rather they
> are computed dynamically, so the optimizations we can introduce
> here will not dominate the overall performance of the program.

I may agree with that, but you should understand that strings are not
the most general case. In the GoF example, glyphs are created from
simple characters. The fact that they come from literals or are computed
dynamically does not make a big difference, they're just characters. In
such a scenario your framework couldn't avoid creating an unnecessary
temporary glyph.

>
>>> * What is your evaluation of the implementation?
>> The implementation seems well done. The use of the "named
>> parameters" idiom is very interesting.
>>
>> However, I strongly disagree with the choice of the term "factory"
>> for a component that actually only acts as a storage. In GoF the
>> term factory is properly used for a component which is devoted to
>> construct the final object given its identifying key. But in this
>> proposal it's the user code that actually constructs the object,
>> so the term is used incorrectly and is misleading.
>
> I decided to keep "factory" because this component is obviously
> related to the element of the same name in GoF and oher descriptions
> of the pattern. Incidentally, when point 3 above gets implemented
> the factory will act more like a factory in the sense you describe.
> That said, I'll be happy to use whatever other terminology reviewers
> agree upon.

I would keep the "factory" term if you are considering the find/insert
approach as an alternative to the current insert-only approach. With
that approach case, find() would be given a key, rather than an object.
If the object is not found, insert() would have to create an object out
of the information stored in the key and that would actually be what
factories are expected to do.

>>> * What is your evaluation of the potential usefulness of the
>> library?
>> The library as it is, is not very useful, although it might be
>> potentially very useful if the flaw I described before could be
>> addressed.
>
> I hope you might concede that the lib is useful in the scenarios
> described at point 1 above, even if these are not the modes of
> usage you envisioned in the frist place. Looking forward to your
> discussion of the arguments I presented here.
>

Maybe I was a little to harsh. I concede that. Having reduced memory
usage and manipulating heavy-weight objects through lighter-weight
handles is certainly an advantage over a naive approach. This advantage
can be felt considerably once the flyweight "pool" is established and
all that is left is to manipulate the objects. However, there are cases,
for example the two cases I presented in this post, where the
construction of the actual flyweight objects has a non negligible cost:
in the GoF word-processing case just because of the very large number of
glyphs, in the 3D mesh case because of the very high cost of I/O
operations. In those cases, the current design makes the task of
establishing the flyweight "pool" less efficient than hand-coded
solutions based on maps. As in my projects I am very concerned with
start-up times I view the proposed library design as mere syntactic
sugar and I would definitely prefer the hand-coded solution.

Just my opinion,

Ganesh


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk