|
Boost : |
From: Beman Dawes (bdawes_at_[hidden])
Date: 2003-08-14 09:56:03
At 06:03 AM 8/14/2003, Walter Landry wrote:
>Greetings,
>
>I've started using boost::filesystem recently, and I'm mostly very
>happy.
Wow! A very happy user. Or at least mostly very happy. That's good news:-)
Seriously, it is a powerful motivator to get that kind of feedback.
> One thing bothers me, though. Why does it implement any
>restrictions, by default, on what kind of files it allows?
See below.
>...
>I also noticed that you can't open a directory named "." or "..",
>though I think "./" and "../" both work.
Hum... I'm not sure that is currently intended.
> Files starting or ending with spaces also don't work.
That is intended, although it may change if the portability checking scheme
changes.
>
>I understand that I can (painfully) work around it by using a
>different context, but I don't understand why boost::filesystem wants
>to restrict me to a set of filenames that are portable. Isn't that a
>bit too much handholding? I don't mind having an is_portable()
>command or something similar, but it is incredibly annoying to have to
>abide by someone else's filename restrictions.
The current approach is clearly too restrictive and isn't satisfactory.
Beyond the problems you mention, there really isn't a single standard for
portability. Even 8.3 names aren't portable to systems which don't allow
periods in names. A whole family of checkers is needed, giving various
degrees of portability. Some should be supplied with the library, but users
also need to be able to provide their own.
OTOH, a function that has to be explicitly called isn't going to be
effective. Manual checking is too laborious in code that does a lot of path
manipulation. A one time I took several pages of code and tried to add
explicit checks. The code turned into a mess. Manual checking is also
likely to be ignored by the very programmers who need it the most; those
who have firm but erroneous ideas about what is or isn't portable.
So it comes down to an interface problem. What is the best way for the user
specify a portability checking function to override the default checking
function?
It would be trivial to add an additional path constructor that takes a
checking function or function object.
But that would kill ease-of-use. It is one thing to require an additional
constructor argument in the fairly limited use "native" case, but the
portability checking is applied to each and every path object constructed,
including the many path temporaries created by the automatic conversions
from char * and string.
(That "kill ease-of-use" point might be hard to understand if you haven't
actually written code using the library. The automatic conversions really
do allow "script-like" programming ease, and are reasonably safe given the
conversion result is class path.)
Also, the portability checking policy often needs to propagate to called
functions such as third party liberties. Adding overloads of functions to
take an additional argument is also too messy, and doesn't provide for
automatic pass-through to lower level functions. In most (but not all)
programs it really would be convenient if portability checking policy was a
global. But of course we all know that "global variables are consider
harmful". There are also several valid use cases where a program needs to
switch back and forth between portability policies.
Another approach is to provide a way to tell class path that within a file
scope use a policy other than the default. It would have to be carefully
worked out to avoid ODR violations, and to ensure propagation to called
library functions. I have not been able to come up with a workable version
of this approach.
That was about as far as my thinking had gotten in the past. While
composing this reply, I came up with another possible solution. Let's say
<boost/filesystem/path.hpp> added this:
typedef bool (*is_portable_test)( string & candidate );
class scoped_portability_policy : noncopyable
{
public:
explicit scoped_portabiity_policy( is_portable_test f );
~scoped_portabiity_policy();
};
The effect of constructing a scoped_portability_policy is to push p onto a
stack. The destructor pops the stack. Class path uses the top stack entry
as the current portability checker. The stack is initialized with a default
portability checker.
Most programs which wanted a policy other than the default would just being
something like this:
int main()
{
scoped_portability_policy( posix_portability_check );
...
Although the stack is global, when coupled with scoped_portability_policy,
it is safer than a single global variable (because library functions which
push are guaranteed to pop.)
Of course it can be subverted by static or heap allocation. (Aside: Is
there a reliable way to prevent static or heap allocation?)
I'm curious to hear what others would think of the above scheme.
--Beman
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk