Velocity Reviews

Velocity Reviews (http://www.velocityreviews.com/forums/index.php)
-   C++ (http://www.velocityreviews.com/forums/f39-c.html)
-   -   Re: Why is RAII called RAII? (http://www.velocityreviews.com/forums/t733158-re-why-is-raii-called-raii.html)

Goran Pusic 09-13-2010 06:39 AM

Re: Why is RAII called RAII?
 
On Sep 12, 7:37*pm, Rolf Magnus <ramag...@t-online.de> wrote:
> In fact, in many cases, the resources at not acquired at
> initialization time but later on during object lifetime.


(Warning: I am off-exact-topic).

These cases are wrong most of the time. You are talking about two-
phase initialization. Each two-phase initialization brings in one more
state into the program (object available, but unusable), which is
incidental complexity. In my experience, when one thinks this is
needed, there is usually a different design where it's not, and impact
on code size and performance is negligible and can go either way.

Goran.

Richard 09-13-2010 06:52 PM

Two-phase initialization (was: Why is RAII called RAII?)
 
[Please do not mail me a copy of your followup]

Goran Pusic <goranp@cse-semaphore.com> spake the secret code
<cc859551-3c88-422b-b5df-449295b44f96@j2g2000vbo.googlegroups.com> thusly:

>These cases are wrong most of the time. You are talking about two-
>phase initialization. Each two-phase initialization brings in one more
>state into the program (object available, but unusable), which is
>incidental complexity. In my experience, when one thinks this is
>needed, there is usually a different design where it's not, and impact
>on code size and performance is negligible and can go either way.


Sometimes its convenient to call a virtual method as part of the
construction process. You can't do this from a c'tor in C++ so you
have to have some sort of two-phase initialization. When this is the
case, I've hidden the details of the two-phase initialization inside a
static factory method of the class and made the c'tor private (or
protected since you have virtual methods and its implied that derived
classes want to override those virtual methods and therefore will need
some way to call the c'tor of the base class).

I'd be interested in hearing about design alternatives for this
situation as the two-phase intialization approach prohibits creating
such objects on the stack and forcing them into the heap.
--
"The Direct3D Graphics Pipeline" -- DirectX 9 draft available for download
<http://legalizeadulthood.wordpress.com/the-direct3d-graphics-pipeline/>

Legalize Adulthood! <http://legalizeadulthood.wordpress.com>

Balog Pal 09-13-2010 09:17 PM

Re: Why is RAII called RAII?
 
"Goran Pusic" <goranp@cse-semaphore.com>
>
> In fact, in many cases, the resources at not acquired at
> initialization time but later on during object lifetime.


>(Warning: I am off-exact-topic).


>These cases are wrong most of the time.


Are they now?

>You are talking about two-phase initialization.


2-phase init is definitely a 'school' that lingers on, especially because
many homegrown coding standards prohibit exceptions.

But it is not everything -- many clases can pick up stuff en-route, or use
alternative resources, or a different set at different states.

>Each two-phase initialization brings in one more
>state into the program (object available, but unusable), which is
>incidental complexity.


Even the traditional 2-phase init has its good use cases, especially if you
can chose it. See MFC's CFile. You have both ctor that opens file
immediately from passed info and throws on error. And another that just
creates the object and you can Open() later. And can reuse it too, after
close.

You can have CFile members in an object that will open the file eventually,
that is not part of 'init' but its natural life cycle.

>In my experience, when one thinks this is
>needed, there is usually a different design where it's not, and impact
>on code size and performance is negligible and can go either way.


I have a library of many RAII handler objects. And my experience is that
the simplest case -- just having ctor and dtor -- are the least used.

If I write a new one, I generally make it use the auto_ptr interface:

- ctor with optional taking ownership
- reset()
- release()
- dtor

And it serves best. Clients can still use it for pure RAII. Can use just the
RRID (resource release is destruction (C) Matthew Wilson ;). Can reuse via
reset. Or even transfer stuff or handle spacial cases via release().

At my new place i did not (yet) import my suite, deciding we use boost
anyway, that has smart poitners, including scoped_ptr. I thought it will
be the same as my auto_prtNC (NC for no-copy) -- but no, it has the reducer
interface, and it turned out a general PITA.

The important feature of 'RAII' is not construction (not even the
interface), but the strict unique ownership imposed by design and coding
policy -- so you can always tell who is responsible for what, and with the
RRID behavior, most of problems go away automagically.

As for the original question: term RAII got stuck, despite using it we quite
rarely mean RAII in the original sense, but more the mentioned RRID or SBRM
or something alike.



Goran Pusic 09-14-2010 07:54 AM

Re: Why is RAII called RAII?
 
On Sep 13, 11:17*pm, "Balog Pal" <p...@lib.hu> wrote:
> "Goran Pusic" <gor...@cse-semaphore.com>
>
>
>
> > In fact, in many cases, the resources at not acquired at
> > initialization time but later on during object lifetime.
> >(Warning: I am off-exact-topic).
> >These cases are wrong most of the time.

>
> Are they now?
>
> >You are talking about two-phase initialization.

>
> 2-phase init is definitely a 'school' that lingers on, especially because
> many homegrown coding standards prohibit exceptions.


And yet, they are blissfully using new without ever wondering what
that does to their exception safety (last time i checked, e.g. a well-
established framework Qt did that). But fair enough, that is
tangential, and might not be true on some other code either.

What is more to the point: I believe, those who prohibit exceptions,
most of the time did not think that decision through. In C++, you can
only forbid exceptions if:

1. you don't use (or completely isolate in try/catch blocks) standard C
++ library, and many other-a-library;
2. you rig new to do something else on failure except throwing (e.g.
terminate)
3. you forbid copy construction (because that typically throws)
4. you forbid any operator overloading if it might throw (minor point
5. ... (can't think of more, but isn't the above enough?)

First three points are really constraining, so much so, that they are
IMO just poor programming.

> Even the traditional 2-phase init has its good use cases, especially if you
> can chose it. * See MFC's CFile. You have both *ctor that opens file
> immediately from passed info and throws on error. *And another that just
> creates the object and you can Open() later. *And can reuse it too, after
> close.


Yes. My contention is: this is incidental complexity that serves not
enough purpose. E.g. why would you want to "reuse" CFile object? It's
infinitely more expensive to actually open a file on the file system
than to construct a C++ object. There has to be a rather particular
use-case (IMHO) to warrant two-phase construction.

And indeed, there are stream class hierarchies in other frameworks
(VCL of Borland, Java, .NET), containing file-backed streams, that
have no separate "open", and I don't remember people complaining about
any issues.

Goran.

Martin B. 09-14-2010 09:18 AM

Re: Why is RAII called RAII?
 
On 14.09.2010 09:54, Goran Pusic wrote:
> On Sep 13, 11:17 pm, "Balog Pal"<p...@lib.hu> wrote:
>> "Goran Pusic"<gor...@cse-semaphore.com>

>[...]
>> Even the traditional 2-phase init has its good use cases, especially if you
>> can chose it. See MFC's CFile. You have both ctor that opens file
>> immediately from passed info and throws on error. And another that just
>> creates the object and you can Open() later. And can reuse it too, after
>> close.

>
> Yes. My contention is: this is incidental complexity that serves not
> enough purpose. E.g. why would you want to "reuse" CFile object? It's
> infinitely more expensive to actually open a file on the file system
> than to construct a C++ object. There has to be a rather particular
> use-case (IMHO) to warrant two-phase construction.
>


An (open) file object neccessarily needs to provide two-phase
construction. Because quite often failing to open the file is *not* an
error and so I don't want an exception, I just want the error reported
to my immediate code.

Reusing a file object: log file object that should be closed when you
are not currently writing a line to it.

br,
Martin

Goran Pusic 09-14-2010 12:22 PM

Re: Why is RAII called RAII?
 
On Sep 14, 11:18*am, "Martin B." <0xCDCDC...@gmx.at> wrote:
> On 14.09.2010 09:54, Goran Pusic wrote:
>
> > On Sep 13, 11:17 pm, "Balog Pal"<p...@lib.hu> *wrote:
> >> "Goran Pusic"<gor...@cse-semaphore.com>

> >[...]
> >> Even the traditional 2-phase init has its good use cases, especially if you
> >> can chose it. * See MFC's CFile. You have both *ctor that opens file
> >> immediately from passed info and throws on error. *And another that just
> >> creates the object and you can Open() later. *And can reuse it too, after
> >> close.

>
> > Yes. My contention is: this is incidental complexity that serves not
> > enough purpose. E.g. why would you want to "reuse" CFile object? It's
> > infinitely more expensive to actually open a file on the file system
> > than to construct a C++ object. There has to be a rather particular
> > use-case (IMHO) to warrant two-phase construction.

>
> An (open) file object neccessarily needs to provide two-phase
> construction. Because quite often failing to open the file is *not* an
> error and so I don't want an exception, I just want the error reported
> to my immediate code.


(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);

First two simply do not scale wrt complexity of error reporting. 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_.

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 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. And even the calling code is better off expecting an
exception (because one is possible anyhow).

> 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. Any performance hit on
creating CFile object is easily swamped by the actual OS and disk
work. And finally, since we're talking logging, chances are that you
need this to be a no-throw zone. There, again, you need a big try/
catch around all file operations, so you just can't gain anything.

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).

IOW, when done properly, bar IWannaGoFasterday, exceptions beat error-
return any day of the week ( with added bonus of absence of 2-phase
init ;-) ).

Goran.

gwowen 09-14-2010 01:45 PM

Re: Why is RAII called RAII?
 
On Sep 14, 1:22*pm, Goran Pusic <gor...@cse-semaphore.com> wrote:
> 3. (correct code)
> CFile f(params);
> use(f);


// Sometimes the inability to open a file can be handled locally
// What about this use case?

static const char* filelist[3] =
{"file1.txt","file2.txt","file3.txt"};

ifstream foo;
for(int idx=0;idx<3;++idx){
foo.open(filelist[idx],ifstream::in)
if(foo.isopen()) {
// frobnicate file contents
} else {
std::cerr << filelist[idx] << " cannot be opened" << std::endl;
}
}

// versus
static const char* filelist[3] =
{"file1.txt","file2.txt","file3.txt"};
for(int idx=0;idx<3;++idx){
try {
ifstream(filelist[idx],ifstream::in)
// frobnicate file contents
}
catch(std::exception&e) {
std::cerr << filelist[idx] << " " << e.what() << std::endl;
}
}

// Consider writing a Unix CLI program that takes a list a of files
// on which to operate as arguments (like 'ls', or 'rm' or
// other obscurities). Would you *really* write that using
exceptions
// to catch non-existent files?

Balog Pal 09-15-2010 04:27 AM

Re: Why is RAII called RAII?
 
"Goran Pusic" <goranp@cse-semaphore.com>

>> Even the traditional 2-phase init has its good use cases, especially if
>> you
>> can chose it. See MFC's CFile. You have both ctor that opens file
>> immediately from passed info and throws on error. And another that just
>> creates the object and you can Open() later. And can reuse it too, after
>> close.


>Yes. My contention is: this is incidental complexity that serves not
>enough purpose.


C++ is traditionally multi-paradigm, so you are allowed to follow your
attitude, but leave others to have their ways too.

>E.g. why would you want to "reuse" CFile object?


For example to close the log file every 24 (or 1 or whatever) hours and open
another. Or when it gets idle.

>It's
>infinitely more expensive to actually open a file on the file system
>than to construct a C++ object. There has to be a rather particular
>use-case (IMHO) to warrant two-phase construction.


This is not multiphace contruction, but a separate use case. Or better put,
mutable state of your object during its lifetime.

Your logic could go out to the mutable/immutable class concepts. Both are
valuable, and have their good use. Trying to talk down one because the
other is useful hardly helps.

>And indeed, there are stream class hierarchies in other frameworks
>(VCL of Borland, Java, .NET), containing file-backed streams, that
>have no separate "open", and I don't remember people complaining about
>any issues.


People are not complaining because whenever that interface is bad for their
use approach they pick some other tool and move on.

In a deal of cases in my program I chose not to implement 'clear()' like
facility in a class, rather have it as smart_ptr<T>, and reset( new
T(...) ). Especially if that member is tied to some specific states, and is
reset(0) on transition to others.

Using indirection and smart pointers can cover up for some narrowed
interfaces, like missing copy or missing ability to a delayed init.

It's a up to judgement which variant have more complexity -- I'd rather just
skip it ;-)



Goran.


Balog Pal 09-15-2010 05:09 AM

Re: Why is RAII called RAII?
 

"Goran Pusic" <goranp@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 kinda
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 may
provide whatever crap. Detailed error info is rarely needed, the most basic
message box is okay.

>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 ability
to abandon).

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 that
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 free
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 can --
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 cases
due to forced blocks, and if you have multiple objects I better not even
start thinking on the resulting code. ;-)

> 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 locally.


>> 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?

>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 from 1.0.
(switching to LPCTSTR somewhere past 4.2). That works through implicit
conversion from CString. You certainly do not want to create extra CStrings
if have the filename as literal. And even if CFile may create some
CString within its black box, you have no chance to avoid that.



Goran Pusic 09-15-2010 07:45 AM

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 kinda
> 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 may
> provide whatever crap. Detailed error info is rarely needed, the most basic
> 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 ability
> 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 that
> 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 free
> 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 can -- *
> 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 cases
> 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 locally.
>
> >> 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 from 1.0.
> (switching to LPCTSTR somewhere past 4.2). That works through implicit
> conversion from CString. *You certainly do not want to create extra CStrings
> if have the filename as literal. * * And even if CFile may create some
> 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.


All times are GMT. The time now is 10:02 PM.

Powered by vBulletin®. Copyright ©2000 - 2014, vBulletin Solutions, Inc.
SEO by vBSEO ©2010, Crawlability, Inc.