A while ago finished a contract to write a small subset of STL targeted at embedded C++. This proved an interesting challenge. For example, in full STL, you insert a value into a list using, you'll never guess, list::insert.
template<typename type>
list<type>::iterator
list<type>::insert(iterator position, const type & value)
{ ...
}
This returns an iterator to the inserted copy of the value or it throws an exception (allocating the interal list node could throw std::bad_alloc for example). However, you can't use this insert in embedded C++ for two reasons.
My first choice was to emulate exceptions. However, for various understandable reasons, my client decided not to adopt an exception-like mechanism. I'll explain what I did shortly, but first I'd like to make a short diversion and explain why emulating exceptions is my strong preference.
It's all to do with failure [1]. The role of failure is fundamental to writing good software. When you use STL list<type>::insert you don't have to worry about its return value if it fails because if it fails it doesn't return. This allows you to write sequences of expressions and statements without having to constantly check if anything went wrong. This is really important. To use an analogy, consider taking a short walk to the local shop for a pint of milk. Do you:
My conclusion is this: The software model of use that exceptions bring with them is so valuable that if you're working in an environment (or language subset) that doesn't support exceptions you should consider ways in which you can emulate them. The alternative is constanty checking if you've died.
But as I said, my client decided not to adopt an exception-like mechanism. So what did I do? I considered making list::insert return an iterator equal to end() if the insertion failed but decided this was not a good idea. When things are different they should be clearly different. One option is to return a bool and a list::iterator inside a pair-like structure. However, as it turned out, my client's particular list requirement didn't require the returned iterator, so the version of list<type>::insert I wrote looked like this:
bool
list::insert(iterator position, const type & value);
Lacking exceptions, this list::insert returns a bool to signify success or failure. The problem now of course is that the bool return is just too easy to ignore. In contrast, exceptions, by design, are impossible to ignore. In full STL if you're not interested in the iterator return value, ignoring it is precisely what you do. I thought about this for a while before realizing there was a solution. The problem is that if you ignore a bool nothing happens. So the answer is not to use a bool! Use something that looks like a bool, smells like a bool, feels like a bool, and shouts loudly when ignored. (In practice, this version for embedded C++ has to be modified slightly because embedded C++ does not support the mutable keyword either.)
#ifndef LOUD_BOOL_INCLUDED #define LOUD_BOOL_INCLUDED class loud_bool { public: // construction/destruction loud_bool(bool decorated); loud_bool(const loud_bool &); ~loud_bool(); public: // conversion operator bool () const; private: // inappropriate loud_bool & operator=(const loud_bool &); private: // state bool result; mutable bool ignored; }; #endifloud_bool.cpp
#include "loud_bool.hpp" #include <ios> #include <iostream> loud_bool::loud_bool(bool decorated) : result(decorated) , ignored(true) { // all done } loud_bool::loud_bool(const loud_bool & other) : result(other.result) , ignored(other.ignored) { other.ignored = false; } loud_bool::~loud_bool() { if (ignored) { std::cerr.setf(std::ios_base::boolalpha); std::cerr << "WARNING: return " << result << "; is being ignored" << std::endl; } } loud_bool::operator bool () const { ignored = false; return result; }example.cpp
#include "loud_bool.hpp" loud_bool example1() { //... return true; } int main() { bool result = example1(); // no warning example1(); // generates warning return 0; }
The loud_bool class generalizes to the following template class for those of you working in full C++ (I've not put it in a namespace to save horizontal space).
warn_if_ignored.hpp#ifndef WARN_IF_IGNORED_INCLUDED #define WARN_IF_IGNORED_INCLUDED template<typename result_type> class warn_if_ignored { public: // construction/destruction warn_if_ignored(const result_type & decorated); warn_if_ignored(const warn_if_ignored &); ~warn_if_ignored(); public: // conversion operator result_type () const; private: // inappropriate warn_if_ignored & operator=(const warn_if_ignored &); private: // state result_type result; mutable bool ignored; }; #include "warn_if_ignored-template.hpp" #endifwarn_if_ignored-template.hpp
#if !defined WARN_IF_IGNORED_INCLUDED || defined WARN_IF_IGNORED_INCLUDED_TEMPLATE #error "warn_if_ignored-template.hpp" #included directly #endif #define WARN_IF_IGNORED_INCLUDED_TEMPLATE #include <ios> #include <iostream> template<typename result_type> warn_if_ignored<result_type>::warn_if_ignored( const result_type & decorated ) : result(decorated) , ignored(true) { // all done } template<typename result_type> warn_if_ignored<result_type>::warn_if_ignored( const warn_if_ignored & other ) : result(other.result) , ignored(other.ignored) { other.ignored = false; } template<typename result_type> warn_if_ignored<result_type>::~warn_if_ignored() { if (ignored) { std::cerr << "WARNING: return " << result << "; is being ignored" << std::endl; } } template<typename result_type> warn_if_ignored<result_type>::operator result_type () const { ignored = false; return result; }example2.cpp
#include "warn_if_ignored.hpp" warn_if_ignored<bool> example2() { //... return false; } int main() { bool result = example2(); // no warning example2(); // generates a warning return 0; }
[1] To Engineer is Human. The Role of Failure in Successful Design. Henry Petroski. Vintage. 0-679-73416-3
| |
| Jagger Software Ltd | |
| Company # 4070126 | |
| VAT # 762 5213 42 |