Dear Harpist,
First, I share your expierience of being a local expert, and so I'm not sure about my own ideas. But I put them in anyway and would like to receive your and other readers' comments.
I currently don't want to say anything about the general design, as I liked your comments on Paul's code. Some remarks about some subtle points might follow when I have seen more of your design. Here, I only want to share some thoughts about exception declarations. These thoughts relate to your following definition of class Customer:
class Customer { string name; string payee; Customer(Customer const &); Customer & operator=(Customer const &); public: Customer(); ~Customer() throw(); string const &getName() const throw(); string const &getPayee() const throw(); };
Is it a good idea to put an empty exception specification to the read access functions? You write "...reading data should not cause an exception". But then you continue "...[this] might not always be the case", which is certainly true. With the empty exception declaration you give garanties about your class which unnecessarily narrow your possibilities to change your implementation later. E.g., you might later decide to store your Customer objects on a database, and your access functions will read directly from that DB. Then, these functions might well throw an exception. But if you then change your interface and define "string const & getName() const throw (DB_lost);" you might break existing code. And if you leave your empty exception declaration, and catch the possible exceptions inside getName, you have to handle the exception inside the class Customer, which might not be the best place to handle environmental exceptions such as loosing the connection to the DB, and you prevent your client application, which might well be prepared to cope with the DB exception, to receive the exception and handle it. So, I believe its not so good an idea to add any exception specification to the access functions.
The same reasons which apply to the access functions hold true for the destructor as well. It might well be that in a future implementation of your Customer class the destructor has to commit some DB transactions and so has to throw an exception in case of a connection failure. I think, the long time proclaimed rule that a destructor should not throw any exception simply is not true. And this probably is excactly the reason why the standards committee added the "uncaught_ exception()" function. So, your destructor might do anything like this:
Customer::~Customer() // no exception specification! { if (uncaught_exception()) { try { // do everything necessary, but // perhaps performing a rollback // instead of a commit, something // probably went wrong. } catch(...) { // this might do nothing, or might // set a global flag or anything // else to signal the ignored // exception to the client app // do not rethrow the exception! } } else { // normal destructor execution, // which well might throw an // exception } }
So, you prevent an exception leaking out of a destructor only in case of stack unwinding due to another exception, but allow normal exception handling otherwise.
What's the bottom line? Should you omit exception specifications completely? Perhaps, this is the easiest way which gives you maximum flexibility for the future. But this flexibility is not always required. For me (and this goes much to general design questions), there are different kinds of C++ classes. You have general application classes (which typically map directly to corresponding classes from the analysis), which essentially give the interface which is used by all your application programs. For these classes, you need maximum implementation flexibility.
On the other hand, you have basic building blocks, or components, which you use to implement the general application classes. E.g. you might have classes like SimpleCustomer (which just implements the interface of Customer straightforward directly in memory), DBCustomer (which maps the class to a DB table), CorbaCustomer (which uses a Customer object anywhere in your distributed network), etc. These classes state their implementation in their names and interfaces, and so might well give garanties about exceptions without locking future implementation changes more than they are anyway locked by the name.
Of course, there are cases where (empty) exception declarations are absolutely necessary, e.g. for most member functions of an exception class itself, but this is not the scope of my remarks here.
These are my thoughts on exception declarations, but certainly there are other opinions on this topic, and I would like to see them.