Re: Why is RAII called RAII?
On Sep 15, 7:09 am, "Balog Pal" <p...@lib.hu> wrote:
"Goran Pusic" <gor...@cse-semaphore.com>
<quote>
(Working with CFile)
1.
CFile f;
if (!f.Open(params))
return cant_open; // Incomplete. (why error? what file?). Bad code.
use(f);
2.
CFile f;
CFileException e;
if (!f.Open(params, e))
return error_info_form(e); // Better, but complicated.
use(f);
3. (correct code)
CFile f(params);
use(f);
</quote>
Certainly all the snippets are correct. The last one is just your
preference.
First two simply do not scale wrt complexity of error reporting.
Come on. :) Exceptions are king when you report far upwards. They kind=
a
suck if you must try{} -wrap a single line. As a rule of thumb I'd say=
if
you have more than a few hits on 'try' there are problems with something'=
s
design.
First
off, you need at least variant 2, or else, you have crap error info.
Second, "use" might have failures, too (file on network, second write
fails), and these failures vary wildly. How do you plan to pass
quality info to higher layer? If You need error info that can explain
every source of the error, and that is _hard_.
My experience and practice is quite different.
I use the throwing open when the file is expected to be there and
openable -- it was configured, or discovered by a directory scan, or just
written by another component.
I use the nonthrowing form after FileOpenDialog analog, where the user ma=
y
provide whatever crap. Detailed error info is rarely needed, the most bas=
ic
message box is okay.
Ah. I believe this is not good enough. The problem is IMO two fold:
1. people are helpless; depending on the quality of the info, they
might, or might not understand what program says. So they will call
support, or ask questions. At that point, person who tries to answer
_will_ need exact error info, not "could not open file".
2. even if user knows a thing or two about his software/computer,
strange things _will_ happen. When they do, having as precise info as
possible is king.
When resources allow, I'd go for "show user-friendly error to user,
but also log exact error for support".
No 3. gives you error info to the best of CFile abilities, including
file name, OS error code, and +/- appropriate text for _any_ error
that might happen.
And No 2 gives the very same info -- and you can even throw it after some
more work, if you like (say you offered the user to pick again with abili=
ty
to abandon).
Agreed, but IMO always at the expense of more work. Try a use case,
I'm pretty sure you can't make it significantly easier either way.
You can assemble your use case in a sensible way without cluttering the
program. The File wrappers i wrote always had both throwing and
nonthrowing variants, and all had their uses. For example ReadBytes th=
at
expect to successfully read the requested amount from a formatted file or
throw -- and the usual Read that may read less and report the amount.
Open is also may or may not be expected to succeed normatively, and I
generally reserve exceptions to actual problems. Keeping most functions f=
ree
of try {} blocks and be transparent.
And if, by any chance, block that uses the file
must be a no-throw zone, even if you use Open, you still have to use
try-catch.
Didn't we start discussion you claiming less complexity? Indeed you ca=
n --
and it's like dropping a piano on one's head. Even in friendly cases.
Actually throwing ctors are not so easy to work around with try in all ca=
ses
due to forced blocks, and if you have multiple objects I better not even
start thinking on the resulting code. ;-)
Hmmm... In my experience, throwing ctors are quite easy to work with.
What is essential, though, is that any exceptions are precise enough,
so that you can either report them to user, either use them
programmatically for further action.
And even the calling code is better off expecting an
exception (because one is possible anyhow).
That depends on the contract, and what uyou expect to recover from locall=
y.
Reusing a file object: log file object that should be closed when you
are not currently writing a line to it.
Who cares? You need to have file name anyhow. So from there, you go:
CFile(params).write(something);
This is easier than having an unusable CFile that lurks somewhere and
repeatedly calling open close, and whatnot.
Maybe easier for your practice, and not easier for other people's. How
about not playing Procrustes?
That wasn't my intention at all. Look, please explain how is:
class whatever
{
CFile f;
CString name;
write(params)
{
if (!f.Open(name))
handle error; // return here? throw? wha'ever...
f.write(params); // what about errors here?
f.close();
}
};
better than
class whatever
{
CString name;
write(params)
{
CFile(name).write(params);
}
};
?
My contention is: you can change use-case which ever way you want,
using CFile, you can't get this to be more simple, not for the code in
question and not for the caller, either. Let's try it.
There is a place, though, where you could improve (MFC specific): you
often want to pass CString to ctor, not LPCTSTR (to avoid touching
heap and string copy that is otherwise hiding behind).
You what? CFile (and btw most MFC) uses LPCSTR in most interface fr=
om 1.0.
(switching to LPCTSTR somewhere past 4.2). That works through implicit
conversion from CString. You certainly do not want to create extra CSt=
rings
if have the filename as literal. And even if CFile may create som=
e
CString within its black box, you have no chance to avoid that.
No, what you are saying is simply false in practice. Indeed, implicit
conversion is there. When you do have a string literal, CFile will
turn it into a CString on Open (that hits allocation and copying; yes,
CFile has a CString within it's black box). If you already have a
CString, it goes through LPCTSTR conversion and hits allocation -
again. But if there was a const CString& variant of Open, then,
because CString uses COW, allocation would have been avoided.
That stems from a more general notion WRT CString of MFC: const
CString& is preferable to LPCTSTR, and that, because in MFC code, a
LPCTSTR param is most likely backed by a CString, on either calling or
call side. If so, use of const CString& lowers string copying and
allocation.
In MFC, LPCTSTR should be used only when you already have a literal,
and you are sure that __none__ of the code down-stream will turn it
into a CString. That's a tall order, hence const CString& is choice no
1.
Don't believe me. If you do use LPCTSTR in your interfaces, count
allocations and copies when you pass LPCTSTR across calls, change to
const CString& and count again. You'll be surprised.
Goran.