Lecture Notes for Advanced Programming II
3 April 2001 - Wrappers
- what is wrapping
- turning a library or set of routines into another library or set of
routines
- object-oriented languages are good for wrapping because objects are
natural wrappers having features different from libraries and routine
sets
- why wrap
- make a given library or routine set more usable - sockets
- hide the differences between different implementations of the same
features - unix vs microsoft posix implementations; unix vs unix
implementations
- merge similar but different libraries into a generic library -
graphical or thread libraries
- for example, dbm the unix database library
- dbm - manages (key, data) pairs
-
dbminit(
f)
- open the database named f
-
dbmclose()
- close the opened database
-
fetch(
k)
- fetch the data associated with key
k
-
store(
k,
d)
- associate data d with
key k
-
delete(
k)
- delete key k and associated data
-
firstkey()
- return the first key
-
nextkey(
k)
- return the key after key k
- there are at least three versions, all different
- dbm - the original and obsolete
- ndbm - dbm's replacement; named files
- gdbm - gnu's ndbm; more functions than ndbm, different error handling
- wrapper interface design
- what's at the interface
- intersection approach recommended, but produces the lcd
- one-file dbm access
- modified intersection - makes dbm expensive, but dbm is obsolete
- what's the interface look like
- consistency with existing libraries - but the libraries are
inconsistent
- the intersection approach again - this time gdbm loses
- missing functions can either be dealt with (exists), ignored (error
function), or handled behind the scenes (reorganize)
- if the function's useful or expensive, make it visible
- solution - make the interface look like ndbm with cleaned up error
handling; extra gdbm functions are ignored or used behind the scenes
- wrapper implementation
- keep an eye out for shared, global data - the error value
- the error return problem - only one return value
- constructor failures
- the constructor can't return values - indicating failure is a problem
- can't ignore it - leads to undefined behavior; robust failure
- a separate error return member function
-
class simple_dbm {
public:
simple_dbm() : error(0) {
if ((error = f())) return ;
}
int get_error(void) const { return error; }
private:
int error;
}
- a common solution, familiar but perhaps too complicated
- return an error code via a constructor argument
-
class simple_dbm {
public:
simple_dbm(int * error = NULL) {
if ((e = f()) {
if (error != NULL) *error = e;
return;
}
}
}
- now the user can either get errors or not
- an unfamiliar idiom, but not an obscure one
- but every function should treat errors consistently
- also, user has to take the address of the error argument
- reference parameters can take care of the last problem
-
class simple_dbm {
public:
simple_dbm(int error & = default_error) {
if ((error = f()) return;
}
}
private:
static int default_error;
}
int simple_dbm::default_error;
- the external declaration is clumsy, but this is a nice solution
- the initializer-list problem
- you can also use exceptions
- exceptions are almost always a bad idea
- exceptions are like super-gotos - they can branch outside the
enclosing procedure
- the
try-catch
statement is ugly, cluttering up the code much
more than does error-return checking code
- virtual functions
- you can wrap each library directly in a class, or use virtual classes
- virtual functions make clear the porter's responsibility
- virtual functions make it easy to configure among possibilities
- code includes
simple-db.h
- program links with
gdbm-simple-db.o
or ndb-simple-db.o
This page last modified on 6 April 2001.