Dynamic Types

Though it might be a quite controversial issue and its name may after this explanation seem not the most appropriate3.2, there are three main reasons for the decision of implementing and using Dynamic Types (DTs for short) in CLAM.

  1. There is a need in some core classes of the library, of working with classes with a large number of attributes, i.e.: the descriptors of audio segments, where in most cases only a small subset is needed. In this sense it could represent a waste of space if memory is always allocated for all attributes. DT can instantiate and de-instantiate attributes at run-time, and do it in such a way that its interface is the same as if they where normal C++ attributes.
  2. We want support for working with hierarchic or tree structures. That means not only composition of DTs but also aggregates of them (lists, vectors, etc. of DTs). With such compositions of DTs, we can use assignation, and two clone member functions: ShallowCopy() and DeepCopy(), the good thing is that they come free; we don't need to write these members in none of the DT concrete classes.
  3. We need introspection of each DT object. That is the ability to know the name and type of each dynamic attribute, to iterate through theses attributes, and to have some type specific handlers for each. One clear application of introspection is storage support for loading from, and storing to a file, of a tree of DTs. Of course all this is implemented generically, so it appears transparent to the user. XML support, for instance, is implemented. Other profits we take from introspection in DT are debugging aids.
All Processing Data classes in CLAM are DT, as well as the configuration classes for both Processings and Processings Data.

For instantiating and de-instantiating dynamic attributes the developer declaring a Dynamic Type class has to use a set of macros that then, on pre-compile time, expand all of the functionality.

We will describe how Dynamic Type classes work and how they can be used through an example. Imagine we want to model a musical note with a DT3.3. We declare the DT class like this:


\begin{spacing}{0.8}
\textsf{\footnotesize class Note : public DynamicType}{\foo...
...esize\par
}
\par
\textsf{\footnotesize\};}\\
{\footnotesize\par
}
\end{spacing}

As it can be seen, three different macros are used in Dynamic Types: DYNAMIC_TYPE for expanding the concrete DT constructors, DYN_ATTRIBUTE for declaring each dynamic attribute and DYN_CONTAINER_ATTRIBUTE for declaring any STL interface compliant container.

1. DYNAMIC_TYPE is a macro that expands the default constructor of the concrete DT being declared. The first parameter is the total number of dynamic attributes, and the second one the class name. If the writer of a DT derived class sees the need of writing a customized default constructor or other constructors it can be done using special purpose initializers. english

2. DYN_ATTRIBUTE is used to declare a dynamic attribute. It has four parameters, the first one is the attribute order (needed for technical reasons of the DT implementation), the second one is the accessibility (public, protected or private) the third one is the type: it can be any C++ valid type including typedef definitions but not references or pointers. englishThe forth and last parameter is the attribute name, it is important to begin in upper-case because this name (let's call it XXX) will be used to form the attribute accessors GetXXX() and SetXXX(), thus the XXX must start in upper-case.

3. DYN_CONTAINER_ATTR: The purpose of this one is to give storage (only XML by now) support to attributes declared as containers of objects. For that, we need that container to fulfill the STL container interface, so all the STL collection of containers is usable. This macro has five parameters, one more that DYN_ATTRIBUTE: the attribute numeration, accessibility, the type, the name of the attribute and finally the new one: the label of each contained element that will be stored.
Returning to the example above, each DYN_ATTRIBUTE macro will expand a set of usable methods:


\begin{spacing}{0.8}
\textsf{\footnotesize float\& GetPitch(), void SetPitch(con...
...h(Storage\&), bool LoadPitch(Storage\&);
}\\
{\footnotesize\par
}
\end{spacing}

Of course GetPitch and SetPitch are the usual accessors to the data. AddPitch and RemovePitch will instantiate and de-instantiate the attribute, combined with UpdateData that will be explained latter on. HasPitch returns whether Pitch is instantiate at this moment. Finally StorePitch and LoadPitch are for storage purposes.

Once, the concrete DT Note has been declared, we can use it like this:


\begin{spacing}{0.8}
\textsf{\footnotesize Note myNote; // create an instance of...
...); myNote.AddNSines(); myNote.AddSines();}\\
{\footnotesize\par
}
\end{spacing}

Or in the case that we want all of them, is better to use AddAll. This method is not macro generated as AddPitch, but is available in any concrete DT.

myNote.AddAll();

As this kind of operations requires memory management we want to update the data, with its possible reallocations only once for every modification of the DT shape or structure (what can mean lots of individual adds and removes). We'll use the DynamicType member UpdateData for that purpose:


\begin{spacing}{0.8}
\textsf{\footnotesize std::cout < {}< myNote.HasPitch()
//...
...< myNote.HasPitch()
// writes out: 'true'}\\
{\footnotesize\par
}
\end{spacing}

And now all the instantiated attributes can be used normally using the accessors GetXXX and SetXXX. For example:


\begin{spacing}{0.8}
\textsf{\footnotesize myNote.SetNSines(10);}{\footnotesize\...
...otesize int j=myNote.GetNSines();  // ok.}\\
{\footnotesize\par
}
\end{spacing}

2004-10-18