[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: Funny situation (was: Re: Serialization et al)
> > That's actually quite clever. Still, I don't like Directory being a
> > derivative of File. Directories are not files (atleast, not on all
> > OSes), and you don't do the same things to or with them.
> >
> It just seems so simple and straightforward to me:
>
> - a directory contains files.
> - a directory is a file.
>
> Compared to:
>
> - a directory contains files and directories.
> - a directory is similar to a file, but isn't one.
That would be compared to:
- a directory can contain any number of entities of a filesystem.
- a directory is not a file.
Which seems perfectly sensical to me.
There is not a is-a relationship between dirs and files. Sorry. There is
a commonality, yes, but not a is-a relationship. According to my
textbooks, in suchs a situation, the correct way to handle this is to
capture the similar functionality in one class, and make the two derive
from this.
I had a look at the composite pattern. Or, well, an article detailing it
pretty much, and actually going through how to create a file-system with
it (as an example of how to utilise it). In this article, directories
were not files, but they both derived from a common abstract base class.
(I don't have the GoF's book, so I might've got something wrong here).
This is very similar to what we have now. The only exception is that in
the article, the common base class included some methods that really
only made sense for directories (like iterating over and adding/removal
of members).
The article was written by one of the GoF.
> > Also, whenever I'm passed a File pointer, I don't want to be wondering
> > (and in some cases having to check) wheter its really a Directory.
>
> If you don't have to check, you don't have to wonder either. It is a
> File, as far as you are concerned. If you want to do a thing that is
> specific to a directory (like recurse into it), you check.
>
> void recursivelyDoSomethingWithFile(File* file) {
> Directory* dir = file->isDirectory();
> int i;
>
> if(dir)
> for(i = 0; i < dir->getCount(); i++)
> recursivelyDoSomethingWithFile(dir->getEntry(i))
> else
> doSomethingWithFile(file);
> }
>
> Maybe the iterator isn't very nice, but this would work and looks very
> clear and simple to me.
>
In exactly what way is this not possible using the current interface?
Besides, that is a bad way to do it. What if we decide to add a
completely third file-like-but-not-really-anyway File derivative? You'd
have to check for that too, even if you just needed to call virtual
members of File.
In the current scheme, you'd use a DirEntry (something like that, a
common base of File and Directory). Anything in DirEntry is supposed to
make sense for all its derivatives (they should atleast do something
sensical that doens't break existing code), and thus old code will never
have to be reworked (in theory, anyway ;)
You get exactly the same result with the current scheme, but you get
better type-safety, less chance of something doing something you didn't
expect it to do (like nothing) and more stable code (as in never or
rarely needing to change it).
> Yes, that's true! And that's also part of the 10% I'm not going to
> cover. Just do nothing, return either an error or success, depending on
> the call. Ahh, the ease of implementing NOTHING, can't be beat. ;-)
>
It's even easier to not implement anything of the nothing. ;)
That sentence *does* makes sense. :)
> > > This makes for one simple system. There could be a asDirectory() method
> > > to get a properly casted Directory* or a NULL if the File is really only
> > > a File (similar to the S_ISDIR macro applied to the POSIX structure
> > > equivalent to those classes, struct stat). Or dynamic_cast could be
> > > used.
> >
> > Dynamic cast is *evil*. Atleast, it is in my world. Sometimes, you have
> > to use RTTI, but often enough, you don't. To me, its like a goto, just
> > with more proper utilizations.
>
> Ah. I like to think of myself as evil sometimes. :-)
>
> The specific asDirectory() call (returning a Directory* or NULL if it
> isn't a directory) is a no-dynamic_cast way of doing it, very solid and
> static, yet dynamic nonetheless. I just see dynamic_cast as the "generic
> C++ way of doing this". I don't mind either, but some people find
> dynamic_cast evil and some other don't like the asDirectory() "hidden
> cast" saying that "C++ has dynamic_cast to do this"... Since I didn't
> know what side you were on, I proposed both. :-)
>
I dislike both. AsDirectory() is just a pretty facade for dynamic_cast.
dynamic_cast is actually better (read: more effecient) if implemented
properly. dynamic_cast should be able to just test the virtual function
table pointer, while AsDirectory() makes a virtual call through this
table, and returns a value.
It's not that I would hessitate to use any of these approcahes if I
really need to, it's just that I'll try my best to find a solution that
doesn't need it and that isn't inferior.
> > "However, not all checking can be done at compile time. Trying to do so
> > is a good design and implementation strategy, but if taken to excess it
> > can lead to inflexibility and inconvinience."
> >
> > [About RTTI] "Was this facility added to encourage the use of runtime
> > type checking? No!" [Goes on to say it was for stopping vendor specific
> > solutions, and it does have it uses]
> >
> > "The basic rule is as ever: Use static (compile-time checked) mechanisms
> > wherever possible - such mechanisms are feasible and superior to the
> > runtime mechanisms in more cases than most programmers are aware of."
>
> The asDirectory() method is an intermediate way of doing this: it is
> static, as you can't do asDirectory() on just about any object to ask it
> if it is a directory, but it let you have the dynamic advantages of
> dynamic_cast.
>
It *is* RTTI. It's not static in the way Bjarne meant it: He was talking
about compile-time checked type safety. This quite clearly is not what
AsDirectory() does.
> One application RTTI come handy is in *very* general cases, for example
> the streaming mecanism in Turbo Vision, that takes a TObject* and
> streams it. It isn't sufficient to call the virtual void write(TStream*)
> of the TObject, you also have to identify the object and write down its
> type ID first in the stream.
>
Wouldn't a virtual GetID() be much better?
> This could be done in a more general way, but it turns out the safest
> way is the "unsafe" dynamic_cast!
>
dynamic_cast isn't unsafe. It's just evil :)
> > Sometimes, you will want to iterate over only the files or only the
> > directories contained in a Directory. This is VERY ineffecient if those
> > two are put together. Of course, having them apart makes it a little
> > less effecient to iterate over all entries, but not nearly as badly.
>
> Yes, that's true. Another case of 10% missing features.
>
All those 10%s (notice the plural 's' there) are begging to add up...
> Iterators
> methods or classes (I don't do any STL, so you can be sure I'm not
> talking STL iterators here) could get over this easily, by providing
> ways of iterating only directories or only files, which would do an
> if(asDirectory()) in the worst case, but would profit of an internal
> implementation using separate directories and files containers.
>
We already have this, except for the fact that Directories are not
Files.
> > Are you also suggesting moving functionality from
> > FileSystemEntityContainer to Directory? (I'm not sure) If you are, I
> > don't see the disadvantage of abstracting out the specific algorithm
> > used in a Directory derivative.
>
> I am suggesting moving the iteration and container interface to
> Directory. Internally, something like FileSystemEntityContainer could be
> used, but they would be specific to the Directory derivative, I do not
> care about how they actually do that.
>
I believe there are already several iterators to iterate over all the
members of a Directory. Actually, I think I've implemented const and
non-const versions of these for iterating over files, sub-directories
and both.
> I knew it was used and "blatantly reused" the name because deep inside,
> I'd like PakFile to be called PakArchive. Just ignore me. :-)
>
Hmm... I actually like that. The part about PakArchive (not the ignore
stuff :).
> > > You call that straightforward?
> >
> > Why don't you find this to be so? Its perfectly intuitive to me...
>
> I like a single loop and code of the minimum size. To me, a single loop
> with a neat if() in it looks more elegant than two loops with no if().
> For example, using File as the superclass of Directory (which would be a
> container for File*) has its features.
>
> If you add named pipes as a NamedPipe class to this system, would they
> be files or another thing? They are not seekable, have no size and
> plenty of other differences even worse than those that made you make
> Directory a "different" thing, so if you choosed to make this a subclass
> of File, I would find the choice of not making Directory also a subclass
> of File quite suspicious.
>
My main consearn of the Directory<->File difference is that Files, in my
world atleast, *IS* data. If you give me a file, what you are really
talking about is data. I don't really care where that data is coming
from (ie, the device), what I do care about is that its data I'm
getting.
When you are giving me a directory, you are not giving me data. You are
giving me a collection. For me, to have Directory derive form File is
like having std::vector<T> derive from its template argument. It just
doesn't compute. Atleast not for me. container<->data. A collection of
something is quite different from just one of something.
> On the other hand, making this NamedPipe class a "different" class,
> inheriting from DirEntry, like File and Directory are, would mean adding
> a FileSystemEntityContainer to Directory and *three* loops in user code.
> This would definitively freak me out.
>
If you needed only functionality that really *is* common for
directories, files and this third "something", you'd be fine. No need
for more loops. All that would be declared in the common base class.
> > > Even the best C/C++ compilers, like Kai's or Compaq's (previously
> > > Digital)
> >
> > I believe Microsoft's is pretty good too.
>
> VC++ isn't at all at the same level as Kai, Portland or Compaq. It's
> closer to the GNU compilers (in the same league I might say). Those
> compilers I listed are compilers made for HPC development, where
> everything is tweaked to the bone. They take age to compile and their
> code run faster than their shadow (for C/C++).
>
According to an article I saw on LGDC MS's compiler severely outperforms
the GNU compilers, and it was the quickest in the test. Unless my memory
has completely failed me, that is. The article migth be a bit dated,
though (not sure).
> > > can't approach the level of performance many Fortran compilers
> > > can do. By the way, GCC/EGCS is not there at all when it comes to high
> > > performance computing (not enough support for things like vectors and
> > > specialized architectures and instructions).
> >
> > Sadly so :(
>
> They are sufficient for games. Nobody has a vector machine handy to play
> games anyway. ;-)
>
I think you should ask a games programmer about that. The only reason
games run on todays computers are that the programmers crippled the
software in some way to make it so that they could. Games can *always*
use more effeciency and processing power. Actually, they always *need*
this.
Ok, this isn't true for all games, but give an AI any complex problem, a
game will always be able to use more processing power.
Somthing like: how best to coordinate an attack again my enemy, when I
have 100+ units, and 20+ unit types, all with distinct abilities and
disabilities. I also need to consider the large amount of the map I
don't know about, aswell as the enemy units, that may be of any of 20+
different unit types, that I don't even know the number of. On top of
this, I have to judge the financial and industrial abilities of my enemy
to produce new units in response to my attack, aswell as the possibility
that he may be further ahead, or lagging behind, in research, making my
units better/worse than his, even if they would normally be similar.
My enemy might also attack me, which I need to plan for. He migth be
able to draw on reinforcements from outside the map that I didn't know
about. The battle is multi-dimensional, meaning I have to think about
both air and land. My enemy might also use weapons like a nuke to
disrupt the functioning of any part of my military facilities.
And this is the already crippled versions we have today... I mean, this
isn't overly realistic. In a more realistic simulation, the computer
would have to take into account many, many, many more variables, and it
migth even have trouble identifying what "real"-world action maps to
what variable. Ideally, we'd have a real-time completely ray-traced
battlefield where each unit was autonomous, could figure out clever
strategies by themselves (ie, each and every unit runs its own personal
human-brain comparative), and the enemy would have to use real-world
technology like recognising the uniform (just like the human player has
to) to identify the different types of soldiers.
In short, I don't think *anything* is sufficient for problems like
these. Heck, a human will never be sufficient (atleast not normal ones
like me :).