> The base class actually provides more functionality than this (database connection for instance).
> The other functions serve as a contract for derived classes,
> it makes sense that crudl classes implement crudl operations. I
> made it a template because a class person_crudl would work with person objects,
> whilst a class animal_crudl would work on animal objects
My question was: these crudl classes are designed to be compile-time polymorphic.
Then why does the base class require virtual functions?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
template < typename ListType > struct crudl_base
{
// common operations
// database connection etc.
// read and write xml data
// etc...
// none of them are virtual
// they could perhaps be static?
};
class person_crudl : crudl_base<personlist>
{
/* static? */ operation_status create( /* ... */ ) { /* ... */ }
// etc...
};
template< typename T, typename L, template <typename,typename> class CRUDL >
void foo( C<T,L>& crudl, const T& object )
{
crudl.create( object /* ... */ ) ; // compile time polymorphic
};
|
> Did you mean to say that the data inputted to these functions should be XML?
Yes.
XML is text based and is not implementation-specific. An integer in XML is a number; size, representation, endianness do not come into play. Text data can be portably transferred over the network and through firewalls.
XML is easy to version; the database schema and the C++ class are decoupled, and one can be modified without affecting the other.
XM is structured data can be stored and retrieved from text files, relational or object databases, spreadsheets etc., can be easily displayed on a web page, can be put in a SOAP envelope etc.
XML may not be appropriate if the data set is huge, but I presumed that that would not be the case here. If we write an overload of :
1 2
|
template < ITEM_TYPE > std::string to_xml( const ITEM_TYPE& object ) ;
template < ITEM_TYPE > ITEM_TYPE from_xml( const std::string& xml ) ;
|
for each type (animal, person etc),
then the bulk of basic crudl operations can be factored into common reusable code.
> The errors I refer to are not "program errors" however, they are more like status reports of the function.
Returning a status code is fine, then. As long as the no other information other than the the status code would be required to handle the error.
> I suppose having a database failure would result in not having the resource available to display a record to the user, so that would constitute a situation that is "exceptional".
Yes. Or else, the problem of discriminating between errors would remain.
enum status { /* ... */ NETWORK_ERROR, DATABASE_ACESS_FAILURE /* ... */ };
If a remote database is not accessible because the network is down, what status code would you return? Creating a new status code when this scenario arises would beak a lot of existing error handling code.
With exceptions:
1 2
|
struct network_error : public virtual std::runtime_error { /* ... */ } ;
struct db_access_error : public virtual std::runtime_error { /* ... */ } ;
|
We can discriminate further by adding derived classes as and when the need arises.
1 2 3 4 5 6 7 8 9
|
void foo( /* ... */ )
{
// ...
struct network_db_access_error : public virtual network_error, public virtual db_access_error { /* ... */ } ;
throw network_db_access_error( /* ... */ ) ;
// ...
}
|