Object-oriented design patterns in the kernel, part 1
Posted Jun 2, 2011 13:37 UTC (Thu) by Cyberax (✭ supporter ✭, #52523)
[Link]
This kind of code is BAD.
For this reason:
====================
void somefunction() {
do_something();
if (do_something_else())
return; //Whoopsie!
important_cleanup_code();
}
====================
In good C++ one would write something like:
====================
void somefunction() {
ON_SCOPE_EXIT(&important_cleanup_code);
do_something();
if (do_something_else())
return; //No whoopses.
do_something_really_else();
}
====================
If you use smart pointers it's even nicer:
====================
void shared_ptr<File> somefunction() {
const std::string &name=compute_file_name();
if (some_condition(name))
return shared_ptr(new File(name, FOR_READ));
if (anotherCondition())
throw my_exception(F("Something bad happened with the file ")<<name);
return shared_ptr(new File(name, FOR_WRITE));
}
//Call site
shared_ptr<File> fl=somefunction(); //that's it!
//One can even do
somefunction(); //For side effects, for example - returned file won't be lost
====================
In pure C you'll have to do something like:
====================
void int somefunction(file_object_ptr *result_place)
{
some_string name;
int res;
name=compute_file_name();
if (some_condition(name))
{
//And pray that your numeric error codes do not collide.
res=file_open_file(name, FOR_READ, result_place);
//What if we want to pass a verbose message
//like 'file <NAME> is locked by another process'?
goto cleanup;
}
if (anotherCondition())
{
res=E_SOMETHINGBAD;
//Verbose errors? Forget about it.
goto cleanup;
}
//Call site
file_object_ptr obj=0; //Don't forget to initialize!
int res=somefunction(&obj);
if (res<0) return res; //Hope...
cleanup:
delete_file(obj);
====================
Object-oriented design patterns in the kernel, part 1
Posted Jun 2, 2011 14:20 UTC (Thu) by Trelane (guest, #56877)
[Link]
Pardon my ignorance, but where does ON_SCOPE_EXIT come from? I'd really like it in some places. :)
Thanks!
Object-oriented design patterns in the kernel, part 1
Posted Jun 2, 2011 15:48 UTC (Thu) by nye (guest, #51576)
[Link]
Object-oriented design patterns in the kernel, part 1
Posted Jun 2, 2011 15:56 UTC (Thu) by Cyberax (✭ supporter ✭, #52523)
[Link]
Originally, from Alexandrescu's article. I've been using it since 2001: http://drdobbs.com/cpp/184403758 . It's simple and it works everywhere (even on VisualStudio 6).
Object-oriented design patterns in the kernel, part 1
Posted Jun 2, 2011 19:04 UTC (Thu) by Trelane (guest, #56877)
[Link]
Thanks for the pointers! (heh) It will make my code suck much less. :)
Object-oriented design patterns in the kernel, part 1
Posted Jun 2, 2011 22:42 UTC (Thu) by cmccabe (guest, #60281)
[Link]
Golang has the "defer" statement, which sets up some statements which will be executed when the current scope exits. So you might do something like this
I have to be honest; I don't think dumping that pile of macros and templates into your code will make it "suck less." I think you just need to use the mechanisms C++ gives you, namely try and catch, and RAII. It may be an extra line or two, but the programmers trying to read your code after you've moved on will thank you.
Especially when you're debugging something, flow control macros are not your friends. Alexandrescu may be able to figure it out in gdb, but he probably doesn't work with you.
Object-oriented design patterns in the kernel, part 1
Posted Jun 2, 2011 22:53 UTC (Thu) by Cyberax (✭ supporter ✭, #52523)
[Link]
To be fair, ON_SCOPE_EXIT is quite nice. Its functionality is very simple and restricted and it's indispensable when one needs to do something simple, usually with non-C++ resources (like calling fclose on FILE*).
It's also easy to debug. Though I tend to spend much less time in a debugger when working with C++ code, all this strict typing pays off.
C++0x has lambdas so it's possible to write analog of 'defer' from Golang (indeed, Boost already has it).
Object-oriented design patterns in the kernel, part 1
Posted Jun 3, 2011 1:16 UTC (Fri) by Trelane (guest, #56877)
[Link]
Thanks to both of your for the info; I am learning a lot. Yet again LWN proves to be the only place where the comments have value. :)
Object-oriented design patterns in the kernel, part 1
Posted Jun 3, 2011 9:46 UTC (Fri) by cortana (subscriber, #24596)
[Link]
FYI, you can still use shared_ptr with FILE* (and probably any other C API that follows the same pattern.
shared_ptr<FILE> f (fopen ("whatever", "r"), fclose);
Now, fclose will be called whenever the last copy of that shared_ptr is destructed. No more single exit points, goto tricks, or leaning towers of if statements!
Object-oriented design patterns in the kernel, part 1
Posted Jun 3, 2011 12:30 UTC (Fri) by cmccabe (guest, #60281)
[Link]
That's pretty good, and it relies on things that are actually in the standard library. Thanks.
Object-oriented design patterns in the kernel, part 1
Posted Jun 3, 2011 14:19 UTC (Fri) by cortana (subscriber, #24596)
[Link]
This and std::vector are the best things in C++! :)
Object-oriented design patterns in the kernel, part 1
Posted Jun 6, 2011 8:58 UTC (Mon) by michaeljt (subscriber, #39183)
[Link]
> This kind of code is BAD.
>
> For this reason:
> ====================
> void somefunction() {
> do_something();
> if (do_something_else())
> return; //Whoopsie!
> important_cleanup_code();
> }
> ====================
In C you can get round that with the following (you don't have to like it if you don't want to!)
> //And pray that your numeric error codes do not collide.
> res=file_open_file(name, FOR_READ, result_place);
> //What if we want to pass a verbose message
> //like 'file <NAME> is locked by another process'?
The devil's advocate in me says: my (admittedly limited) experience is that you can't do that even in C++ without thinking very carefully about error handling before you implement it - including memory management issues, particularly if you may end up handling out of memory conditions using that mechanism. And if you are going to think about it beforehand anyway you can also return a pointer to an error structure (which can potentially be statically or dynamically allocated) in C rather than just an integer error code. (That was just an example of course as there are lots of other ways to achieve the same thing.)
Object-oriented design patterns in the kernel, part 1
Posted Jun 6, 2011 14:47 UTC (Mon) by Cyberax (✭ supporter ✭, #52523)
[Link]
>In C you can get round that with the following (you don't have to like it if you don't want to!)
>void somefunction_wrapper() {
>somefunction_core();
>important_cleanup_code();
>}
That's even worse. You'll have either put resources into global variables (yikes!) or pass around some kind of 'local frame' structure. Essentially reinventing exceptions.
>The devil's advocate in me says: my (admittedly limited) experience is that you can't do that even in C++ without thinking very carefully about error handling before you implement it - including memory management issues, particularly if you may end up handling out of memory conditions using that mechanism.
OOM conditions are not really worth it to handle in userspace applications.
In kernel level handling OOM would be mandatory, but not so much different from the current situation. Kernel would just set aside a small pool of RAM and use it during OOM allocations.
>And if you are going to think about it beforehand anyway you can also return a pointer to an error structure (which can potentially be statically or dynamically allocated) in C rather than just an integer error code. (That was just an example of course as there are lots of other ways to achieve the same thing.)
The problem is that it quickly becomes very cumbersome as each and every function has to pass around pointers to error structures.
Object-oriented design patterns in the kernel, part 1
Posted Jun 6, 2011 15:00 UTC (Mon) by michaeljt (subscriber, #39183)
[Link]
>> In C you can get round that with the following (you don't have to like it if you don't want to!)
>> void somefunction_wrapper() {
>> somefunction_core();
>> important_cleanup_code();
>> }
>
> That's even worse. You'll have either put resources into global variables > (yikes!) or pass around some kind of 'local frame' structure. Essentially > reinventing exceptions.
Not quite sure what you mean there.
[...]
>> And if you are going to think about it beforehand anyway you can also return a pointer to an error structure (which can potentially be statically or dynamically allocated) in C rather than just an integer error code. (That was just an example of course as there are lots of other ways to achieve the same thing.)
> The problem is that it quickly becomes very cumbersome as each and every function has to pass around pointers to error structures.
Not really - you just return a pointer where C traditionally returns an integer error code, and pass that on if a function you call returns a non-NULL error pointer.
Object-oriented design patterns in the kernel, part 1
Posted Jun 10, 2011 13:08 UTC (Fri) by jond (subscriber, #37669)
[Link]
> This kind of code is BAD.
> For this reason:
> ====================
> void somefunction() {
> do_something();
> if (do_something_else())
> return; //Whoopsie!
✂ ✂ ✂ ✂ ✂
That isn't the example code I provided, at all. You've injected a return which I did not have in my code. My code, as written, will (in C) guarantee the last line to be executed if the process has not been terminated. Exceptions in C++ prevent you from making the same assertion, which is why you need RAII and/or
> If you use smart pointers it's even nicer:
stuff like smart pointers.
There's a good overview of using smart pointers and other techniques to ensure deterministic resource management here:
Can do_something() fail for some reason? If so, why don't you check for it? If not, why do you think you need to worry about it throwing an exception?
Under what circumstances do you imagine that C++ do_something_cpp() will throw an exception but do_something_c() silently succeed?
Object-oriented design patterns in the kernel, part 1
Posted Jun 16, 2011 9:12 UTC (Thu) by elanthis (guest, #6227)
[Link]
There's big differences.
A C-style error handler can fail without bringing down the rest of the system. An exception cannot. There's also different definitions of "fail" with different levels of consequences, while an exception is always a fatal error unless you explicitly check for it.
There's the matter of exception handling logic not being able to be placed where you might want it, too. With a simplistic C error handling facility I can store an error code and check it later when it matters or is most convenient. With exceptions, I need to wrap each individual function call (and operator, etc.) in order to ensure that nothing slips past.
Operator overloading, virtual functions, etc. are all massively useful features that help an unbelievable amount in writing good code. Exceptions just don't bring any meaningful benefit to large, complex programs that need to keep operating, don't bring any readability/maintainability benefit, and pretty much only appear to be beneficial when you look at tiny little trivial error handling examples compared to very poorly written C APIs. (Operator overloading is often panned, but anyone who's ever written a game in C with C-style math libraries can tell you that operator loading opponents are full of doody; and anyone who's ever tried a math library in Python/Lua/JavaScript/Ruby/Java/C#/etc. can tell you with five times the conviction that dynamic typing or static typing without local types that C++ critics are full of doody.)
Object-oriented design patterns in the kernel, part 1
Posted Jun 16, 2011 13:46 UTC (Thu) by gowen (guest, #23914)
[Link]
There's the matter of exception handling logic not being able to be placed where you might want it, too. With a simplistic C error handling facility I can store an error code and check it later when it matters or is most convenient.
Wow. I am truly staggered ... just utterly lost for words. Exceptions/RAII automatically propogate non-recoverable errors to the correct level of abstraction, error return values have to be propogated by hand.
With exceptions, I need to wrap each individual function call (and operator, etc.) in order to ensure that nothing slips past.
Holy living Christ on a bicycle... Do you really think that's how to write code that uses exceptions for error handling? If you do, please stop writing about it, because you don't know what you're talking about.