LWN.net Logo

Object-oriented design patterns in the kernel, part 1

Object-oriented design patterns in the kernel, part 1

Posted Jun 2, 2011 13:37 UTC (Thu) by Cyberax (✭ supporter ✭, #52523)
In reply to: Object-oriented design patterns in the kernel, part 1 by jond
Parent article: Object-oriented design patterns in the kernel, part 1

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;
}

res=file_open_file(name, FOR_WRITE, result_place);

cleanup:
free_string(name);

return res;
}

//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);
====================


(Log in to post comments)

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

Then there's Boost.ScopeExit: http://www.boost.org/doc/libs/1_45_0/libs/scope_exit/doc/... which is a bit nicer (it even lists alternatives here: http://www.boost.org/doc/libs/1_45_0/libs/scope_exit/doc/... ).

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
> func foo() {
>     defer fmt.Println("running cleanup");
>    ...
> }
Then the printout will happen when you return from the function, whenever that is.

C, of course, has the single exit point idiom, which is used a lot in the kernel. It looks like this:

> int foo() {
>     setup_bar();
>     ...
>     if (ret)
>         goto cleanup;
>     ...
> cleanup:
>     shutdown_bar();
>     return ret;
> }
Java, Python, and Ruby have try...finally { }

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

void somefunction_wrapper() {
somefunction_core();
important_cleanup_code();
}

void somefunction_core() {
do_something();
if (do_something_else())
return; //No whoopses.
do_something_really_else();> return; //Whoopsie!
}

> //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!
&#9986; &#9986; &#9986; &#9986; &#9986;

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:

http://www.slideshare.net/eplawless/exception-safety-and-...

Copyright © 2013, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds