Blog Archive
Comments Now Enabled - what would you like to see?
Thursday, 26 June 2008
I have now updated my blog engine to allow comments on my blog posts, so please give it a whirl.
To kick things off, please add a comment on this entry if there's something you'd like me to cover on my blog, and I'll pick the ones I feel able to write about as topics for future posts.
If you're viewing this post in an RSS reader, you'll have to actually go to the website to comment. If you're viewing this post on one of the blog directory pages, click on the title or follow the "Permanent Link" to get to the entry page.
Any comments I feel are inappropriate or spam will be deleted.
Posted by Anthony Williams
[/ news /] permanent link
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
Exceptions make for Elegant Code
Friday, 06 June 2008
On this week's Stack Overflow podcast, Joel comes out quite strongly against exceptions, on the basis that they are hidden flow paths. Whilst I can sympathise with the idea of making every possible control path in a routine explicitly visible, having just had to write some C code for a recent project I would really like to say that this actually makes the code a lot harder to follow, as the actual code for what it's really doing is hidden amongst a load of error checking.
Whether or not you use exceptions, you have the same number of possible flow paths. With exceptions, the code can be a lot cleaner than with exceptions, as you don't have to write a check after every function call to verify that it did indeed succeed, and you can now proceed with the rest of the function. Instead, the code tells you when it's gone wrong by throwing an exception.
Exceptions also simplify the function signature: rather than having to add an additional parameter to hold the potential error
code, or to hold the function result (because the return value is used for the error code), exceptions allow the function signature
to specify exactly what is appropriate for the task at hand, with errors being reported "out-of-band". Yes, some functions use
errno
, which helps by providing a similar out-of-band error channel, but it's not a panacea: you have to check and
clear it between every call, otherwise you might be passing invalid data into subsequent functions. Also, it requires that you have
a value you can use for the return type in the case that an error occurs. With exceptions you don't have to worry about either of
these, as they interrupt the code at the point of the error, and you don't have to supply a return value.
Here's three implementations of the same function using error code returns, errno and exceptions:
int foo_with_error_codes(some_type param1,other_type param2,result_type* result) { int error=0; intermediate_type temp; if((error=do_blah(param1,23,&temp)) || (error=do_flibble(param2,temp,result)) { return error; } return 0; } result_type foo_with_errno(some_type param1,other_type param2) { errno=0; intermediate_type temp=do_blah(param1,23); if(errno) { return dummy_result_type_value; } return do_flibble(param2,temp); } result_type foo_with_exceptions(some_type param1,other_type param2) { return do_flibble(param2,do_blah(param1,23)); }
Error Recovery
In all three cases, I've assumed that there's no recovery required if do_blah
succeeds but do_flibble
fails. If recovery was required, additional code would be required. It could be argued that this is where the problems with
exceptions begin, as the code paths for exceptions are hidden, and it is therefore unclear where the cleanup must be done. However,
if you design your code with exceptions in mind I find you still get elegant
code. try
/catch
blocks are ugly: this is where deterministic destruction comes into its own. By
encapsulating resources, and performing changes in an exception-safe manner, you end up with elegant code that behaves gracefully in
the face of exceptions, without cluttering the "happy path". Here's some code:
int foo_with_error_codes(some_type param1,other_type param2,result_type* result) { int error=0; intermediate_type temp; if(error=do_blah(param1,23,&temp)) { return error; } if(error=do_flibble(param2,temp,result)) { cleanup_blah(temp); return error; } return 0; } result_type foo_with_errno(some_type param1,other_type param2) { errno=0; intermediate_type temp=do_blah(param1,23); if(errno) { return dummy_result_type_value; } result_type res=do_flibble(param2,temp); if(errno) { cleanup_blah(temp); return dummy_result_type_value; } return res; } result_type foo_with_exceptions(some_type param1,other_type param2) { return do_flibble(param2,do_blah(param1,23)); } result_type foo_with_exceptions2(some_type param1,other_type param2) { blah_cleanup_guard temp(do_blah(param1,23)); result_type res=do_flibble(param2,temp); temp.dismiss(); return res; }
In the error code cases, we need to explicitly cleanup on error, by calling cleanup_blah
. In the exception case
we've got two possibilities, depending on how your code is structured. In foo_with_exceptions
, everything is just
handled directly: if do_flibble
doesn't take ownership of the intermediate data, it cleans itself up. This might well
be the case if do_blah
returns a type that handles its own resources, such as std::string
or
boost::shared_ptr
. If explicit cleanup might be required, we can write a resource management class such as
blah_cleanup_guard
used by foo_with_exceptions2
, which takes ownership of the effects of
do_blah
, and calls cleanup_blah
in the destructor unless we call dismiss
to indicate that
everything is going OK.
Real Examples
That's enough waffling about made up examples, let's look at some real code. Here's something simple: adding a new value to a
dynamic array of DataType
objects held in a simple dynamic_array
class. Let's assume that objects of
DataType
can somehow fail to be copied: maybe they allocate memory internally, which may therefore fail. We'll also use
a really dumb algorithm that reallocates every time a new element is added. This is not for any reason other than it simplifies the
code: we don't need to check whether or not reallocation is needed.
If we're using exceptions, that failure will manifest as an exception, and our code looks like this:
class DataType { public: DataType(const DataType& other); }; class dynamic_array { private: class heap_data_holder { DataType* data; unsigned initialized_count; public: heap_data_holder(): data(0),initialized_count(0) {} explicit heap_data_holder(unsigned max_count): data((DataType*)malloc(max_count*sizeof(DataType))), initialized_count(0) { if(!data) { throw std::bad_alloc(); } } void append_copy(DataType const& value) { new (data+initialized_count) DataType(value); ++initialized_count; } void swap(heap_data_holder& other) { std::swap(data,other.data); std::swap(initialized_count,other.initialized_count); } unsigned get_count() const { return initialized_count; } ~heap_data_holder() { for(unsigned i=0;i<initialized_count;++i) { data[i].~DataType(); } free(data); } DataType& operator[](unsigned index) { return data[index]; } }; heap_data_holder data; // no copying for now dynamic_array& operator=(dynamic_array& other); dynamic_array(dynamic_array& other); public: dynamic_array() {} void add_element(DataType const& new_value) { heap_data_holder new_data(data.get_count()+1); for(unsigned i=0;i<data.get_count();++i) { new_data.append_copy(data[i]); } new_data.append_copy(new_value); new_data.swap(data); } };
On the other, if we can't use exceptions, the code looks like this:
class DataType { public: DataType(const DataType& other); int get_error(); }; class dynamic_array { private: class heap_data_holder { DataType* data; unsigned initialized_count; int error_code; public: heap_data_holder(): data(0),initialized_count(0),error_code(0) {} explicit heap_data_holder(unsigned max_count): data((DataType*)malloc(max_count*sizeof(DataType))), initialized_count(0), error_code(0) { if(!data) { error_code=out_of_memory; } } int get_error() const { return error_code; } int append_copy(DataType const& value) { new (data+initialized_count) DataType(value); if(data[initialized_count].get_error()) { int const error=data[initialized_count].get_error(); data[initialized_count].~DataType(); return error; } ++initialized_count; return 0; } void swap(heap_data_holder& other) { std::swap(data,other.data); std::swap(initialized_count,other.initialized_count); } unsigned get_count() const { return initialized_count; } ~heap_data_holder() { for(unsigned i=0;i<initialized_count;++i) { data[i].~DataType(); } free(data); } DataType& operator[](unsigned index) { return data[index]; } }; heap_data_holder data; // no copying for now dynamic_array& operator=(dynamic_array& other); dynamic_array(dynamic_array& other); public: dynamic_array() {} int add_element(DataType const& new_value) { heap_data_holder new_data(data.get_count()+1); if(new_data.get_error()) return new_data.get_error(); for(unsigned i=0;i<data.get_count();++i) { int const error=new_data.append_copy(data[i]); if(error) return error; } int const error=new_data.append_copy(new_value); if(error) return error; new_data.swap(data); return 0; } };
It's not too dissimilar, but there's a lot of checks for error codes: add_element
has gone from 10 lines to 17,
which is almost double, and there's also additional checks in the heap_data_holder
class. In my experience, this is
typical: if you have to explicitly write error checks at every failure point rather than use exceptions, your code can get quite a
lot larger for no gain. Also, the constructor of heap_data_holder
can no longer report failure directly: it must store
the error code for later retrieval. To my eyes, the exception-based version is a whole lot clearer and more elegant, as well as
being shorter: a net gain over the error-code version.
Conclusion
I guess it's a matter of taste, but I find code that uses exceptions is shorter, clearer, and actually has fewer bugs than code that uses error codes. Yes, you have to think about the consequences of an exception, and at which points in the code an exception can be thrown, but you have to do that anyway with error codes, and it's easy to write simple resource management classes to ensure everything is taken care of.
Posted by Anthony Williams
[/ design /] permanent link
Tags: exceptions, elegance, software
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
Updated (yet again) Implementation of Futures for C++
Friday, 30 May 2008
I have updated my prototype
futures library implementation yet again. This version adds
wait_for_any()
and wait_for_all()
functions, which can be used either to wait for up to five futures known
at compile time, or a dynamic collection using an iterator range.
jss::unique_future<int> futures[count]; // populate futures jss::unique_future<int>* const future= jss::wait_for_any(futures,futures+count); std::vector<jss::shared_future<int> > vec; // populate vec std::vector<jss::shared_future<int> >::iterator const f= jss::wait_for_any(vec.begin(),vec.end());
The new version is available for download, again under the Boost Software License. It still needs to be compiled against the Boost Subversion Trunk, as it uses the Boost Exception library and some new features of the Boost.Thread library, which are not available in an official boost release.
Sample usage can be seen in the test harness. The support for alternative allocators is still missing. The documentation for the futures library is available online, but is also included in the zip file.
Please download this prototype, put it through its paces, and let me know what you think.
Posted by Anthony Williams
[/ threading /] permanent link
Tags: futures, promise, threading, concurrency, n2561
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
C, BASIC and Real Programmers
Tuesday, 27 May 2008
There's been a lot of discussion about learning C, and whether or not BASIC provides a good grounding for learning to program, following Joel Spolsky and Jeff Atwood's Stack overflow podcasts.
Having been one of those who grew up with the first batch of home computers in the 1980s, and therefore learnt to program in BASIC on an 8-bit home-computer, I feel ideally qualified to add my tuppence to the discussion.
I think BASIC was a crucial part of my early interactions with
computers. When you turned the computer on, it sat there expectantly,
with a prompt that said Ready
, and a blinking cursor
inviting you to type something. The possibilities were endless. Not
only that, but you could often view the source code of games, as many
of them were written in BASIC. This would allow you to learn from
others, and crucially hammered home the idea that you could do this
too: they were using BASIC just like you. This is a long way from the
experience of today's first-time computer users: the computer starts
up, and does all kinds of fancy things from the get-go. You don't type
in BASIC commands to make it do things, you click the mouse. Modern
computers don't even come with a programming language: you have to
install a compiler or interpreter first. I am concerned that the next
generation of programmers will be missing out because of this.
BASIC is not enough
However, BASIC is not enough. BASIC teaches you about the general
ideas of programming: variables, statements, expressions, etc., but
BASIC interpreters rarely featured much in the way of structured
programming techniques. Typically, all variables were generally
global, and there was often no such thing as a procedure or function
call: just about everything was done with GOTO
or maybe
GOSUB
. BASIC learnt in isolation by a lone hobbyist
programmer, by cribbing bits from manuals, magazines, and other
people's source code, would not engender much in the way of good
programming habits. Though it did serve to separate
the programming sheep from the non-programming goats, I can see
why Dijkstra was so whipping of it. To be a good programmer, BASIC is
not enough.
To learn good programming habits and really understand about the machine requires more than BASIC. For many, C is the path to such enlightenment: it provides functions and local variables, so you can learn about structured programming, and it's "close to the machine", so you have to deal with pointers and memory allocation. If you can truly grok programming in C, then it will improve your programming, whatever language you use.
I took another path. Not one that I would necessarily recommend to
others, but it certainly worked for me. You see, a home computer came
with not just one language but two: BASIC and machine
code. As time wore on, the BASIC listing of source code for games
would increasingly be a long list of DATA
statements with
seemingly random sequences of the digits 0-9 and the letters A-F,
along with a few lines of BASIC, at least one of which would feature
the mysterious POKE
command. This is where I learnt about
machine code and assembly language: these DATA
statements
contain the hexadecimal representation of the raw instructions that
the computer executes.
Real Programmers do it in hex
Tantalized, I acquired a book on Z80 assembly language, and I was hooked. I would spend hours writing out programs on pieces of paper and converting them into hex codes by looking up the mnemonics in the reference manual. I would calculate jump offsets by counting bytes. Over time I learnt the opcodes for most of the Z80 instruction set. Real Programmers don't need an assembler and certainly not a compiler; Real programmers can do it all by hand!
These days, I use a compiler and assembler like everyone else, but my point still stands, and it is this: by learning assembly language, I had to confront the raw machine at its most basic level. Binary and hexadecimal arithmetic, pointers, subroutines, stacks and registers. Good programming techniques follow naturally: if your loop is too long, the jump instruction at the end won't reach, as there is a limit of 128 bytes on conditional jumps. Duplicate code is not just a problem for maintenance: you have to convert it twice, and it consumes twice as much of your precious address space, so subroutines become an important basic technique. By the time I learnt C, I had already learnt much of the lessons around pointers and memory allocation that you can only get from a low-level language.
It's all in the details
BASIC was an important rite of passage for many of today's programmers: those who learnt programming on their home computer in the 1980s, but it is not enough. High-level programming languages such as C# or Java are a vast improvement on BASIC, but they don't provide programmers with the low-level knowledge that can be gained by really learning C or assembler.
It's the low level details that are important here. If you don't actively program in C, you don't have to learn C per-se, but something equivalently low-level. If you find the idea of writing a whole program in assembler and machine code interesting, go with that: I thoroughly enjoyed it, but it might not be your cup of tea.
C is not enough either
This actually ties in with the whole "learn a new programming language every year" idea: different programming languages bring different ideas and concepts to the mix. I have learnt a lot from looking at how programs are written in Haskell and Lisp, even though I never use them in my work, and I learnt much from Java and C# that I didn't learn from C and assembler. The same applies here: a low level programming language such as C provides a unique perspective that higher-level languages don't provide. Viewing things from this perspective can improve your code whatever language you write in. If you're striving to write elegant software, viewing it from multiple perspectives can only help.
Posted by Anthony Williams
[/ design /] permanent link
Tags: programming languages, C, BASIC, programmers
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
The A-Z of Cool Computer Games
Tuesday, 27 May 2008
My wife picked up this book last week, and it's an absolutely fabulous book. It's a jolly, nostalgic trip down memory lane for those of us (like myself and my wife) who grew up with the first batch of home computers in the 1980s. If you can look back fondly on the touch sssssssssseeeeeeenstiiive keyboard of the ZX81, the nine (count them!) colours of the Dragon-32, the 64K (wow!) and hardware sprites of the Commodore 64, and the delights of games like Manic Miner, Frogger and Hungry Horace, then this book is for you.
This book covers more than just the games, though: there are
sections on the home computers themselves, the social environment
surrounding home computer usage, and the various paraphernalia and
random bits of gadgetry people used to have. Over time, the nature
of computer games has changed quite considerably: no longer can you
look at the source code for a game just by pressing Escape
or Break and typing LIST
at the ensuing BASIC
prompt; no longer do we have to fiddle with the volume and tone
controls on our tape decks in order to get the latest game to load;
and no longer are we limited to 16 colours (or less).
If you've got a bit of time to spare, and fancy a trip down memory
lane to a youth spent destroying joysticks by playing Daley
Thompson's Decathlon too vigorously or typing in listings from
magazines only to get SYNTAX ERROR in line 4360
when
you try and run them, buy this book.
Recommended.
Buy this book
At Amazon.co.ukAt Amazon.com
Posted by Anthony Williams
[/ reviews /] permanent link
Tags: reviews, games
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
Updated (again) Implementation of Futures for C++
Thursday, 15 May 2008
I have updated my prototype futures library implementation again, primarily to add documentation, but also to fix a few minor issues.
The new version is available for download, again under the Boost Software License. It still needs to be compiled against the Boost Subversion Trunk, as it uses the Boost Exception library, which is not available in an official boost release.
Sample usage can be seen in the test harness. The support for alternative allocators is still missing. The documentation for the futures library is available online, but is also included in the zip file.
Please download this prototype, put it through its paces, and let me know what you think.
Posted by Anthony Williams
[/ threading /] permanent link
Tags: futures, promise, threading, concurrency, n2561
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
Updated Implementation of Futures for C++
Sunday, 11 May 2008
I have updated my prototype futures library implementation in light of various comments received, and my own thoughts.
The new version is available for download, again under the Boost Software License. It still needs to be compiled against the Boost Subversion Trunk, as it uses the Boost Exception library, which is not available in an official boost release.
Sample usage can be seen in the test harness. The support for alternative allocators is still missing.
Changes
- I have removed the
try_get
/timed_get
functions, as they can be replaced with a combination ofwait()
ortimed_wait()
andget()
, and they don't work withunique_future<R&>
orunique_future<void>
. - I've also removed the
move()
functions onunique_future
. Instead,get()
returns an rvalue-reference to allow moving in those types with move support. Yes, if you callget()
twice on a movable type then the secondget()
returns an empty shell of an object, but I don't really think that's a problem: if you want to callget()
multiple times, use ashared_future
. I've implemented this with both rvalue-references and the boost.thread move emulation, so you can have aunique_future<boost::thread>
if necessary.test_unique_future_for_move_only_udt()
in test_futures.cpp shows this in action with a user-defined movable-only typeX
. - Finally, I've added a
set_wait_callback()
function to bothpromise
andpackaged_task
. This allows for lazy-futures which don't actually run the operation to generate the value until the value is needed: no threading required. It also allows for a thread pool to do task stealing if a pool thread waits for a task that's not started yet. The callbacks must be thread-safe as they are potentially called from many waiting threads simultaneously. At the moment, I've specified the callbacks as taking a non-const reference to thepromise
orpackaged_task
for which they are set, but I'm open to just making them be any callable function, and leaving it up to the user to callbind()
to do that.
I've left the wait operations as wait()
and timed_wait()
, but I've had a suggestion to use
wait()
/wait_for()
/wait_until()
, which I'm actively considering.
Please download this prototype, put it through its paces, and let me know what you think.
Posted by Anthony Williams
[/ threading /] permanent link
Tags: futures, promise, threading, concurrency, n2561
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
Free Implementation of Futures for C++ from N2561
Monday, 05 May 2008
I am happy to announce the release of a prototype futures library for C++ based on N2561. Packaged as a single header file released under the Boost Software License it needs to be compiled against the Boost Subversion Trunk, as it uses the Boost Exception library, which is not available in an official boost release.
Sample usage can be seen in the test harness. There is one feature missing, which is the support for alternative allocators. I intend to add such support in due course.
Please download this prototype, put it through its paces, and let me know what you think.
Posted by Anthony Williams
[/ threading /] permanent link
Tags: futures, promise, threading, concurrency, n2561
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
Bug Found in Boost.Thread (with Fix): Flaw in Condition Variable on Windows
Monday, 28 April 2008
There's a bug....
First the bad news: shortly after Boost
1.35.0 was released, a couple of users reported experiencing problems using
boost::condition_variable
on Windows: when they used
notify_one()<\code>, sometimes their notifies disappeared, even when they
knew there was a waiting thread.
... and now it's fixed
Next, the good news: I've found and fixed the bug, and committed the fix to
the boost Subversion repository. If you can't update your boost implementation
to trunk, you can download
the new code and replace
boost/thread/win32/condition_variable.hpp
from the boost 1.35.0
distribution with the new version.
What was it?
For those of you interested in the details, this bug was in code related to
detecting (and preventing) spurious wakes. When a condition variable was
notified with notify_one()
, the implementation was choosing one or
more threads to compete for the notify. One of these would get the notification
and return from wait()
. Those that didn't get the notify were
supposed to resume waiting without returning from
wait()
. Unfortunately, this left a potential gap where those
threads weren't waiting, so would miss any calls to notify_one()
that occurred before those threads resumed waiting.
The fix was to rewrite the wait/notify mechanism so this gap no longer exists, by changing the way that waiting threads are counted.
Posted by Anthony Williams
[/ threading /] permanent link
Tags: boost, thread, condition variable, windows
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
The Future of Concurrency in C++: Slides from ACCU 2008
Monday, 07 April 2008
My presentation on The Future of Concurrency in C++ at ACCU 2008 last Thursday went off without a hitch. I was pleased to find that my talk was well attended, and the audience had lots of worthwhile questions — hopefully I answered them to everybody's satisfaction.
For those that didn't attend, or for those that did, but would like a reminder of what I said, here are the slides from my presentation.
Posted by Anthony Williams
[/ threading /] permanent link
Tags: concurrency, multithreading, C++, ACCU
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
Design and Content Copyright © 2005-2025 Just Software Solutions Ltd. All rights reserved. | Privacy Policy