
This section covers some basic aspects of KeyValue's design. The material is kept at the minimum just enough to give the reader all she needs to develop her application using KeyValue.
All KeyValue classes, functions, templates, etc. belong to namespace
::keyvalue.
The five so called basic types are:
bool;
double;
;ptime
.string; and
unsigned int.
Additionally, KeyValue introduces to represent
empty data.value::Nothing
To maximize portability, KeyValue uses
and
::std::string for strings and
times, resp. These types are exported to namespace ::boost::posix_time::keyvalue where they are called
and
string resp.ptime
The single-value and multi-type container for basic types
(excluding unsigned int) is
.value::Variant
The value assigned to a key is not necessarily a single
. It may be a
container of value::Variants as well.
KeyValue provides three such containers:value::Variant
Actually, bridge and core library developers do not need to care
about these containers. Indeed, they are used exclusively inside
KeyValue and, at some point, are converted to more standard types. More
precisely, a is converted into
an appropriate basic type value::SingleT while a
becomes a
value::Vector
and a ::std::vector<T> is transformed
into a
value::Matrix.::std::vector<::std::vector<T>>
Class is a single-value
and multi-type container for value::Value,
value::Single or
value::Vector.value::Matrix
Only s are returned
from KeyValue to front-ends. Hence, a series of conversions must be
performed when one wants to return a more basic type. For instance,
suppose that a double value
value::Value must be returned. In that case the
sequence of conversions would be:x
returnvalue::Value(value::Single(value::Variant(x)));
Statements like the one above would be needed often and this is very annoying. Fortunately, KeyValue implements a hierarchy tree of types that allow for multi-level implicitly conversions. Therefore, in the example above, the compiler automatically replaces the simpler statement
return x;the one previously shown.
The hierarchy of types constitutes a tree where each node is
defined by a specialization of template struct .Parent
Initially a key is just a text labeling a value. However, there is
more inside a key that just a
can model. Consider the key Dates in the
introductory example again. Its associated value is expected to verify
certain conditions:string
The corresponding is made of
value::Values rather than, say,
ptimedoubles.
Given the plural in Dates, one can
expect more than one and
then, ptime's content
might be a value::Value (of
value::Vectors).ptime
Since each date corresponds to a stock price, these dates cannot be in the future.
Additionally, one can expect the dates to be in increasing order.
This kind of information is encapsulated by a certain class. In
KeyValue terminology, these classes are called real keys and belong to namespace ::keyvalue::key.
The class is the base of all real
keys. More precisely, real keys derive from key::Key which, in turn,
derives from key::Traits.key::Key
Actually, is a template class
depending on a few parameters:key::Traits
ElementTypeType parameter which defines the type of elements in the
output container. It can be bool,
double,
,
ptime, classes defined by
the core library, etc.string
ConverterTypeThis template template parameter[3] defines the class responsible to convert the input value container into a more appropriate type for core library's use. (See Section 12.3.1.)
MapTypeThis template template parameter[3] tells how each
object in
the input container must be mapped into an
value::VariantElementType value. (See Section 12.3.2.)
Conversions between KeyValue containers ,
value::Single and
value::Vector to more standard
types are responsibility of container
converter classes.value::Matrix
KeyValue provides three such templates (described below) depending on a parameter ElementType.
key::StdSingle<ElementType>Converts from to
value::SingleElementType.
key::StdVector<ElementType>Converts from to
value::Vector.::boost::shared_ptr<::std::vector<ElementType>>
key::StdMatrix<ElementType>Converts from to
value::Matrix.::boost::shared_ptr<::std::vector<::std::vector<ElementType>>>
If the core library uses non-standard containers, then bridge developers have two choices. They can either use the converters above as a first step and then convert again to desired types; or they can implement new container converters that produce the desired types directly from KeyValue containers. The second option is clearly more efficient.
To learn how to implement new container converters, the reading of the reference documentation of three container converters above it strongly advised. Moreover, their implementations can serve as samples for implementing new ones.
Similarly to lexical conversions but depending on the key,
sometimes, each element of the input container must be mapped to a
special value. For instance, for a key Month, it
may be convenient to map strings
"Jan", "Fev", ..., "Dec" into
numbers 1, 2, ...,
12. This is an example of .key::FlagMap
Mappings are performed by classes which implement a method to
convert from a into other
types. Actually, they are template classes depending on a parameter
named value::VariantOutputType which defines (but not
necessarily matches) the actual output type. The actual output type
might be recovered through the member type
OutputType_.
The map template classes are the following:
key::NoMap<OutputType>Through this map, a holding
a value x is mapped into an object of
type value::VariantOutputType which has the same
lexical value as x. Only front-end
enabled lexical conversions are considered. For instance, a
holding
either the double 10.1 or
the value::Variantstring "10.1" is
mapped into the double
(OutputType in this case)
10.1.
key::FlagMap<OutputType>Some values
are accepted others not. The accepted ones are mapped into
particular values of type
stringOutputType. In the example of key
Month above,
OutputType can be
double, unsigned
int or an enum type.
key::PartialMap<OutputType>Half way between and
key::NoMap. First,
similarly to key::FlagMap and
considering front-end enabled lexical conversions, it tries to
map a key::NoMap value
into an object of type value::VariantOutputType
which has the same lexical value as x. If
it fails, then, like , it tries
to map a key::FlagMap into a
corresponding value of type
stringOutputType. For instance, the value
for NumberOfEdges (of a regular polygon)
must be an unsigned int greater than
2. For some special values (but not all)
there correspond a polygon name (e.g.
"Triangle" for 3 or
"Square" for 4). There is no
special name for a regular polygon with
1111 edges.
key::ObjectMap<OutputType>This is a map where a
identifier is
mapped into a pointer to an object of type
stringOutputType. Notice that this is the
only map where OutputType and
OutputType_ differ.
Some properties are shared by several types of keys. For instance, Price, Weight, Size, etc., accept only positive values. Although one can write one class for each of them, this would imply unecessary code duplication. To avoid this, KeyValue implements a few generic keys and then, only very specific and application-dependent keys need to be written as new real keys.
All generic keys set their label at construction time. They are:
key::Single<OutputType>Key for a single object of type
ElementType. No constraints on the
value are set.
Example: A key labeled Number which accepts
any double value is defined by
key::Single<double>key("Number");
key::Vector<ElementType>Key for a vector of objects of type
ElementType with no constraints on
them. A restriction on the size of the vector might be set on
construction.
Example: A key labeled Names which expects
a vector of 5 strings is defined
by
key::Vector<string>key("Names",5);
key::Matrix<ElementType>Key for a matrix of objects of type
ElementType with no constraints. A
restriction on the matrix dimension can be set at
construction.
Example: A key labeled Transformation which
accepts a 2x3 matrix is defined
by
key::Matrix<double>key("Transformation",2,3);
key::PositiveKey for a positive number.
Example: A key labeled Price is defined
by
key::Positivekey("Price");
key::StrictlyPositiveKey for a strictly positive number.
Example: If the key in the previous example could not accept the value 0, then it would be defined by
key::StrictlyPositivekey("Price");
key::Bounded<ElementType,
Bound1, Bound2>Key for a single bounded value of type
ElementType. Template template
parameters[3]
Bound1 and
Bound2 define the bound types and
can be either ,
key::NoBound,
key::Greater (greater than
or equal to), key::Geq or
key::Less (less than or
equal to).key::Leq
Example: A key labeled Probability
accepting any double value from and
including 0 up to and including
1 is defined by
key::Bounded<double,key::Geq,key::Leq>key("Probability",0.0,1.0);
key::MonotoneBoundedVector<ElementType,
Monotone, Bound1,
Bound2>Key for vectors whose elements are monotonic and/or
bounded. Template template parameter[3]
Monotone defines the type of
monotonicity and can be either ,
key::NonMonotone,
key::Increasing,
key::StrictlyIncreasing or
key::Decreasing.
key::StrictlyDecreasingBound1 and
Bound2 are as in
.
Additionally, a constraint on the vector size can be set at
construction.key::Bounded
Example: A key labeled Probabilities
accepting 10 strictly increasing numbers
from and excluding 0 up to and including
1 is defined by
key::Bounded<double,key::StrictlyIncreasing,key::Greater,key::Leq>key("Probabilities",0.0,1.0,10);
Key-value pairs are stored in s. This class implements
methods DataSet and
getValue() to retrieve values
assigned to keys. Both methods receive a real key and processes all the
information about the expected value encapsulated by the key. For
instance, suppose the variable find()
holds the current date and consider a key
BirthDates which corresponds to a vector of
increasing dates, supposedly, in the past or today.today
An appropriate real key is then:
key::MonotoneBoundedVector<ptime,key::Increasing,key::Leq>births("BrithDates",today);
Therefore, if key BirthDates belongs to a
DataSetdata,
the result of
data.getValue(births);
is
a
such that the elements of the pointed vector are in increasing order and
before (less than or equal to) ::boost::shared_ptr<::std::vector<ptime>>. If
the input does not verify this constraint, then an exception is thrown
to inform the user about the failure. Therefore, the caller does not
need to check the constraints.today
Since the type returned by depends on the real
key it receives, this method is a template function. The same is true
for getValue().find()
The difference between and
getValue() concerns what happens
when the key is not resolved. The former method throws an exception to
indicate the failure whereas the latter returns a null pointer. In
practice, find() is used for
compulsory keys and getValue() for optional ones. A
typical use of find() follows:find()
boolfoo(false); if (bool*ptr=data.find(key::Single<bool>("Foo")))foo= *ptr;
In the
code above is foofalse
unless key Foo is found in
, in which case,
data gets the given value.foo
All builders and calculators derive from class
. This class declares
two pure virtual methods: Processor and
getName(). The former method
returns the name under which the processor is recognized by key
Processor. The second gets the result of processing
a getResult().DataSet
Actually, builders and calculators are specializations of template
classes and
Builder, resp. They depend on
a parameter type named CalculatorTag whose primary role
is distinguishing different specializations.
and
Builder specializations may
implement different features which affects their declarations and
implementations. Therefore, different specializations of
Calculators and
Builders might derive from
different base classes and implement different methods. Rather than
declaring the specializations from scratch, providing all their base
classes and declaring all necessary methods, helper files
Calculatorkeyvalue/mngt/DeclareBuilder.h and
keyvalue/mngt/DeclareCalculator.h should be used.
(See and
Builder for details.)Calculator
A able to process an
empty Processor might be declared a
DataSet. More precisely, if
the Command is
Processor, then its
Builder<Tag> method might do its job ignoring
getObject(const DataSet&
data)data (e.g. ListOfDataSets
and NumberOfDataSets).
Another possibility is when the method does look up values in
but, failing to find any, can
still do its job considering default values for the keys, with or
without intervention of Default data
set.data
In the cases above, might be
declared a Builder<Tag> by deriving from this
class. Similar arguments hold for Command.Calculator<Tag>
When a is a
Processor, front-ends might take
advantage of this fact and provide shortcuts or menu entries to call
the Command without asking for
additional input.Processor
In general, the input data required by s are so rich that must
be stored in a Builder. Nevertheless, in some
cases, a single DataSet might be
enough. For instance, consider a builder that creates a function given
a few points on its graph. Normally, this value::Variant needs the set of
points, an interpolator and an extrapolator. A Builder is necessary to hold
all this information. However, when the function is known to be
constant, then a single number ― the constant ― is enough to build the
function. Rather than creating a DataSet to store a single
DataSetdouble value, it would be more convenient if
the could accept just this
value (or more generally, a Builder). This is,
indeed, the case.value::Variant
Any specialization able to
build from a Builder must derive
from template class value::Variant. This template
class depends on BuilderFromObjectType ― the type build
by the builder ― and on InputType ― the
basic type that the can build from. (In
the previous example, BuilderInputType would be
double.)
is the abstract class
that defines the interface for all types of messages sent to loggers.
Message is a template class
which implements MessageImpl's pure virtual methods.
There are six different specializations of Message with corresponding
typedefs:MessageImpl
They define to append
formated data to themselves. A typical use follows:operator &()
Infoinfo(1); // Create a level-1 Info message. size_ti;::std::vector<double>x; //...info& "x[" &i& "] = " &x[i] & '\n';
Similarly, is an
abstract class whose pure virtual methods are implemented by template
class exception::Exception. This
template class has a member which is an instantiation of
exception::ExceptionImpl. The exact
instantiation is provided as a template parameter of
MessageImpl. There
are two specializations of exception::ExceptionImpl with
corresponding typedefs:exception::ExceptionImpl
(having an
RuntimeError member); andError
(having a
LogicError member).Logic
indicates errors
that can be detected only at runtime depending on user-provided data.
RuntimeError indicates errors that
should be detected at development time. In other terms, a
LogicError means a bug and is
thrown when a program invariant fails. It is mainly used indirectly
through macro LogicError as inKV_ASSERT
KV_ASSERT(i<getSize(), "Out of bound!");
To keep compatibility with exception handlers catching standard
exceptions, derives from
RuntimeError while
::std::runtime_error derives from
LogicError.::std::logic_error
Method provides the
same functionality of exception::ExceptionImpl::operator &().
Example:MessageImpl::operator &()
if (price<=0.0) throwRuntimeError() & "Invalid price. Expecting a positive number. Got " &price;
Other more specific exception classes are implemented to indicate
errors that need special treatment. They all derive from either
or
RuntimeError.LogicError
[3] Template template parameters are one of the least
known features of C++ and deserve a quick note here. Most
template parameters are types. Nevertheless, sometimes a
template parameter can be a template, in which case it is
referred as a template template
parameter. For instance, a template
depending on only
one template template parameter might be instantiated with
Foo
but not with
Foo<::std::vector>.
Recall that
Foo<::std::vector<int>> is a
template class while
::std::vector
is a class.::std::vector<int>