| Previous | Index | Next |
This section is addressed to the users that want
to learn the basics aspects and usage of Dynamic Types (DTs for
short). You may also find a section devoted to Dynamic Types in
the "Developers" part of this document. This latter is aimed to
explain how to create new classes that derive from the base DT,
and goes into some details regarding their functionality.
Though it might be a quite controversial issue, there are three main reasons for the decision of implementing and using DTs in CLAM.
All classes deriving from ProcessingData base class are DT. The concrete ProcessingConfig classes as well as the ProcessingDataConfig classes are also DT.
When we say that dynamic attributes can be instantiated at run time, we mean that we can do so with the previously declared dynamic attributes. Let's see an example. Imagine we want to model a musical note with a DT. We declare it like this:
class Note : public DynamicType
{
public:
DYNAMIC_TYPE (Note, 5)
DYN_ATTRIBUTE (0, public, float, Pitch)
DYN_ATTRIBUTE (1, public, unsigned, NSines)
DYN_ATTRIBUTE (2, public, ADSR, Envolvent)
DYN_CONTAINER_ATTRIBUTE (3, public, std::list<Sine>, Sines,
harmonic)
DYN_ATTRIBUTE (4, private, Audio, Wave)
};
This is a macro-based declaration, right now there are three different macros: 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.
We will now explain these three more in depth:
1. DYNAMIC_TYPE this macro 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 the initializers. See section 82.
2. DYN_ATTRIBUTE it 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 (& finished) or pointers (* finished). If you still think you need pointers as dynamic attributes then read the pointers section (section 85).
The 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, following the coding style of the library (See chapter XVI)
Returning to the example above, each DYN_ATTRIBUTE macro will expand a set of usable methods:
float& GetPitch(), void SetPitch(const float&),
void AddPitch(), void RemovePitch(), bool HasPitch()
void StorePitch(Storage&) const, bool LoadPitch(Storage&)
Of course GetPitch and SetPitch are the usual accessors to the data. AddPitch and RemovePitch, combined with UpdateData that will be explained latter on, will instantiate and de-instantiate the attribute. HasPitch returns whether Pitch is instantiated at this moment. Finally StorePitch and LoadPitch are for storage purposes, and will be explained in section 35
3. DYN_CONTAINER_ATTR: The purpose of this macro is to give storage (only XML by now) support to attributes declared as containers of objects. For providing this service, 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.
Once, the concrete DT Note has been declared, we can use it like this:
Note myNote; // create an instance of the DT Note
Now myNote, have no attribute instanciate. We can activate attributes this way:
myNote.AddPitch(); myNote.AddNSines(); myNote.AddSines();
Or in the case that we want all of them, it is better to use AddAll. (This method is not macro generated as AddPitch, but is a DT member available in any concrete DT.
)myNote.AddAll();
As this kind of operations require memory management, we want to update the data, with its possible reallocations only once for every modification of the DT shape or structure (which can imply many individual adds and removes). We'll use the DynamicType UpdateData operation for that purpose:
std::cout << myNote.HasPitch() // writes out: 'false'
myNote.UpdateData();
std::cout << myNote.HasPitch() // writes out: 'true'
And now all the instantiated attributes can be accessed as usual, using the accessors GetXXX and SetXXX. For example:
myNote.SetNSines(10);
myNote.SetPitch(440);
// lets use some std::list operations:
myNote.GetSines().push_back(440).push_back(440*2);
myNote.GetSines().push_back(440*3).push_back(440*4); // etc.
int i=myNote.GetPitch(); // error! GetPitch() returns float
int j=myNote.GetNSines(); // ok.
More about adding, removing and updating attributes:
It is important to learn the exact behaviour of adding and removing DT attributes. It can be summed up to three ideas:
• GetXXX and SetXXX operations over non-instantiated attributes will rise an exception ErrDynamicType. So these operations should be protected with an if (HasXXX()) clause in places of the code where there is no safety about the presence of XXX.
• Besides, AddXXX and RemoveXXX can be used safely (don't rise any exception). Basically what they do is set and unset internal flags. For example, if the attribute Pitch exists and we do RemovePitch() it will be marked as "removed" (waiting for the UpdateData() to perform the actual removal). Now, if before updating data we call AddPitch(), then it detects the mark and the effect is just to unset this flag. If we insist with another AddPitch() it will have no effect (and no flag will be set), because it is allready instantiated. The case of adding a non-instantiated attribute is symmetric as the case of removing an instantiated one.
• UpdateData() is a safe and efficient operation: with safe what we mean is that it has no effect (and doesn't rise exceptions) in case that the DT needs no memory update, and it is efficient in the sense that the checking for changes in the dynamic shape doesn't involve any traversal of the attributes but only cheking a global flag. Moreover, UpdataData() returns a boolean that says if it has been necessary to update the data. Sometimes it is very useful do things such as :
// here we don't know if Pitch exist.
myNote.AddPitch();
if (myNote.UpdateData()) {
// yes, it existed
// ...
}
else {
// no, it didn't was there till now.
// ...
}
It's been said that a the dynamic shape of a DT is the set of instantiated attributes. The description of this shape is stored in a table and can be shared between various DTs. Thus this brings us to the idea of prototypes and creating objects by cloning the dynamic shape of others, this is exactly how the default copy constructor works.
Note n1(myNote), n2(myNote), n3(myNote), n4(myNote);
Now n1, n2, n3 and n4 all share both static information of their type and the dynamic shape information, and we say that myNote is their prototype, or that they all share the same prototype. It is important to notice that this happens even if the prototype (myNote) doesn't have its data updated. If we decide to change the shape of one of these objects, i.e. we want to add new attributes to n4, this operation will automatically create a new prototype and so a new dynamic shape as the next figure shows:
Of course, the copy constructor also copies the data of each instantiated attribute from the source object into the new object. This copy is made using the corresponding copy constructor. This means that the copy constructor makes a so-called "deep copy" (that means recursives copies of its sub-elements) of any composition of DT and aggregates (i.e. using STL containers) of DTs.
More on copy constructors (i.e. with the shareMemory flag) can be learnt directly from the javadoc source documentation.
This section only explores a feature that is particular to the Dynamic Types: debug-time information. If you should need more details about how to store and load DT from and to XML please refer to chapter IX.
Since the dynamic data of a DT is not stored as regular C++ attributes, it might be difficult to explore in the usual way from the debugger. Thus we have provided the DT base class with a Debug() method that can be called from the debugger environment and basically does two things:
Note that the XML load and store, including storing the debug file, will only work if the CLAM macro CLAM_USE_XML was set when compiling.
| Previous | Index | Next |