Files
mlpack/fastlib/base/otrav.h
T

445 lines
14 KiB
C++

/**
* @file otrav.h
*
* Object-tree traversal.
*
* See the comment on namespace @c ot;
*/
#ifndef BASE_OTRAV_H
#define BASE_OTRAV_H
#include "ccmem.h"
#include <typeinfo>
#include <stdarg.h>
#include <ctype.h>
#define OT__NAME(x) (v_OT->Name( #x ))
/**
* Within OT_DEF, declare a sub-object (or primitive) that is directly
* contained, NOT pointed to.
*
* (For those reading the definition: v_OT is the parameter, the visitor,
* passed to the object traverse member function).
*/
#define OT_MY_OBJECT(x) (OT__NAME(x), v_OT->MyObject(this->x))
/**
* Within OT_DEF, declare a static-sized array embedded within your object.
*
* The length of the array is determined automatically via sizeof.
*/
#define OT_MY_ARRAY(x) (OT__NAME(x), v_OT->MyArray(this->x, sizeof(this->x) / sizeof(this->x[0])))
/**
* Within OT_DEF, declare an object being pointed to, managed by
* new and delete.
*/
#define OT_PTR(x) (OT__NAME(x), v_OT->Ptr(this->x, false))
/**
* Within OT_DEF, declare an array being pointed to, managed by
* new[] and delete[].
*/
#define OT_ARRAY(x, i) (OT__NAME(x), v_OT->Array(this->x, i))
/**
* Within OT_DEF, declare an array or object being pointed to managed by
* malloc and free.
*/
#define OT_MALLOC_ARRAY(x, i) (OT__NAME(x), v_OT->MallocArray(this->x, i))
/**
* Within OT_DEF, declare a pointer to a new-delete object that might be NULL.
*/
#define OT_PTR_NULLABLE(x) (OT__NAME(x), v_OT->Ptr(this->x, true))
/**
* Like OT_DEF but doesn't define any of the automatic freebies.
*
* @see OT_DEF
*/
#define OT_DEF_ONLY(AClass) \
public: \
template<typename Visitor> \
friend void TraverseObject(AClass *obj_OT, Visitor *v_OT) { \
obj_OT->TraverseObject__OT_(v_OT); \
} \
private: \
template<typename Visitor> \
void TraverseObject__OT_(Visitor *v_OT)
/**
* Automatically generate a copy constructor based on the object traversal.
*/
#define OT_GEN_COPY_CONSTRUCTOR(AClass) \
public: AClass(const AClass& other) { ot__private::ZOTDeepCopier::Doit(other, this); } private:
/**
* Automatically create a dstructor based on object traversal.
*/
#define OT_GEN_DESTRUCTOR(AClass) \
public: ~AClass() { ot__private::DestructorImplementation(this); } private:
/**
* Automatically create a dstructor based on object traversal.
*/
#define OT_GEN_ASSIGN(AClass) \
public: const AClass& operator = (const AClass& other) \
{ this->~AClass(); new(this)AClass(other); return *this; } \
private:
/**
* Generate a default constructor.
*/
#define OT_GEN_DEFAULT_CONSTRUCTOR(AClass) \
public: AClass() { } private:
/**
* Automatically create a Copy method based on the copy constructor.
*/
#define OT_GEN_COPY_METHOD(AClass) \
public: void Copy(const AClass& other) { new(this)AClass(other); } private:
/**
* Define the object traversal for this object, and clearly defines its
* lifecycle and resource allocation as FASTlib-compliant.
*
* Example:
* @code
* class MyTree {
* private:
* int value;
* MyTree *left;
* MyTree *right;
* int num_extra_data;
* Data *extra_data_array;
*
* OT_DEF(MyTree) {
* OT_MY_OBJECT(value);
* OT_PTR_NULLABLE(left);
* OT_PTR_NULLABLE(right);
* OT_MY_OBJECT(num_extra_data);
* OT_ARRAY(extra_data_array, num_extra_data);
* }
* };
* ... rest of class definition ...
* @endcode
*
* The OT_DEF is used to declare a class's members, and its pointers.
* Notice that <code>OT_MY_OBJECT(num_extra_data)</code> must come before
* the subsequent line that uses num_extra_data as an array length. If
* deserialization is occuring, each <code>OT_...</code> call is actually
* deserializing each member, so num_extra_data is uninitialized until
* <code>OT_MY_OBJECT</code> is called on it.
*
* Fine-point: If you have an array of pointers, you are pretty much doomed
* to declare the array of pointers and iterate over the array yourself for
* each pointer, treating each element of the array as a separate pointer.
* Note that all your familiar programming concepts like if-statements and
* for-loops are valid in the OT_DEF block because it's just a function,
* just make sure you're careful about only accessing members that have
* already been "traversed".
*
* This macro will also make your class FASTlib-compliant by creating a
* default constructor, copy constructor, assignment operator,
* and Copy method.
*
* @see OT_MY_OBJECT, OT_MY_ARRAY, OT_PTR, OT_ARRAY, OT_MALLOC_ARRAY,
* OT_PTR_NULLABLE, OT_ARRAY_NULLABLE, OT_MALLOC_ARRAY_NULLABLE.
*/
#define OT_DEF(AClass) \
public: \
OT_GEN_DEFAULT_CONSTRUCTOR(AClass) \
OT_GEN_COPY_CONSTRUCTOR(AClass) \
OT_GEN_DESTRUCTOR(AClass) \
OT_GEN_COPY_METHOD(AClass) \
OT_GEN_ASSIGN(AClass) \
OT_DEF_ONLY(AClass)
/**
* Defines object traversal for classes with no pointer members.
*
* This will use the compiler's default copy constructor and default
* destructor, which are almost certainly faster than the ot-based one.
*/
#define OT_DEF_BASIC(AClass) \
public: \
OT_GEN_DEFAULT_CONSTRUCTOR(AClass) \
OT_GEN_COPY_METHOD(AClass) \
OT_GEN_ASSIGN(AClass) \
OT_DEF_ONLY(AClass)
/**
* Declares automatic OT features for a class for classes that have no
* pointers.
*
* This ensures you have a valid,
*/
#define OT_DEFAULTS
/**
* Declare automatic OT features for a class, but for classes that have
* pointers.
*/
#define OT_DEFAULTS_PTR
// Re-think how this is supposed to work.
// /**
// * Create an automatically-generated print method for your class.
// */
// #define OT_GENERATE_PRINT(AClass)
// public:
// template<>
// friend void Print(const AClass& obj, FILE *stream) {
// OTPrint(obj, stream);
// }
// TODO: Automatically generate copy constructors and the like
/**
* Like OT_DEF, but automatically generates as many standard methods as
* possible.
*/
#define OT_FULL(AClass) \
OT_GENERATE_PRINT(AClass) \
OT_DEF(AClass)
/**
* Specify a clean-up step to run after deserialization, for instance, to
* populate transient fields.
*
* An example is ArrayList - it has both a length and capacity. The capacity
* need not be stored, but upon deserialization, the capacity must be
* initialized to a valid value, such as the length.
*
* This is only called when the object has valid pointers. However, this
* must not allocate any resources, because OT_FIX on the class may be called
* multiple times.
*/
#define OT_FIX(AClass) \
public: \
friend void TraverseObjectPostprocess(AClass *x) { \
x->TraverseObjectPostprocess__OT_(); \
} \
private: \
void TraverseObjectPostprocess__OT_()
// The object-tree-visitor interface.
// class OTBlankVisitor {
// public:
// /** visits an object with no OT implementation */
// template<typename T> void Primitive(T& x);
// /** visits an internal object */
// template<typename T> void MyObject(T& x);
// /** visits an array */
// template<typename T> void MyArray(T* x, index_t i);
// /** visits an object pointed to, allocated with new */
// template<typename T> void Ptr(T*& x, bool nullable);
// /** visits an array pointed to, allocated with new[] */
// template<typename T> void Array(T*& x, index_t i, bool nullable);
// /** visits an array pointed to, allocated with malloc */
// template<typename T> void MallocArray(T*& x, index_t i, bool nullable);
// };
/**
* Perform object-tree traversal on a single object with a given object-tree
* visitor.
*
* The visitor can perform pretty much any function it wants with the
* contents of each data type. It can print, serialize, deserialize,
* pointer-freeze, etc.
*/
template<typename T, typename Visitor>
inline void TraverseObject(T* x, Visitor* v) {
v->Primitive(*x);
}
/**
* Postprocess function for making copies, to fix anything that may be
* inaccurate from a plain copy.
*
* You will probably never need to implement this. This exists
* mainly so that lazy-rezing data structures (i.e. ArrayList) can serialize
* themselves as their trimmed size -- the TraverseObject function neglects
* saving the capacity, and fills in the capacity upon deserialization.
* Note this should NOT dereference any pointers within the object, just
* update things like flags.
*/
template<typename T>
inline void TraverseObjectPostprocess(T* x) {
}
/**
* Traverses an array with a particular visitor.
*
* This is a convenience method that just calls TraverseObject on each
* element.
*/
template<typename T, typename Visitor>
inline void TraverseArray(T* x, index_t n_elems, Visitor *v) {
for (index_t i = 0; i < n_elems; i++) {
TraverseObject(&x[i], v);
}
}
#include "otrav_impl.h"
/**
* Object-traversal utilities, such as serialization and deserialization.
*
* This is for traversing a directed acyclic graph of pointers, i.e. the
* actual underlying data structure. It turns out a generalized DAG
* traversal framework allows for the following to be available at no
* additional work on the application programmer:
*
* @li Serialization (save to byte stream)
* @li Deserialization (read from byte stream)
* @li Object freezing/thawing/refreezing
* (storing bulk flattened objects in RAM)
* @li Debug print, or save to s-expression or XML
* @li Destructors and copy constructors
*
* We define the concept of an object-traversal (OT) compliant class:
*
* @li No polymorphism, i.e. virtual functions
* @li No cycles in the pointer graph (actually there are a few exceptions,
* such as trees can have parent pointers, see OT_FIX)
* @li No inheritance, although non-polymorphic inheritance may be a
* possibility
* @li Blank default constructor that puts object into an "invalid" state,
* with Init methods
*
* These classes can be thought of as "pure data structures". If your class
* is not OT-compliant, you probably want to put the FORBID_COPY header in
* your class. This is not to say that non-OT-compliant classes aren't
* useful -- it's definitely true that some objects aren't really meant to
* be copied or sent over the network. Think for example the Thread class.
*
* To define the object traversal for a class, use @c OT_DEF (which generates
* function headers) and fill out the function with
* @c OT_MY_OBJECT(member_field) and other relevant macros within the
* body. See THOR for many simple examples, and see col/arraylist.h
* for a rather complicated example.
*/
namespace ot {
/**
* Prints any OT-defined object.
*
* Perfect for debugging.
*/
template<typename T>
void Print(const T& object, FILE *stream = stderr) {
ot__private::ZOTPrinter printer;
printer.Doit(object, stream);
}
/**
* Prints any object, for use in print statements.
*
* What this does is return the message you provide it, so that it is
* reasonable to put this in the variable-arguments list of a printf
* messge:
*
* @code
* DEBUG_ASSERT_MSG(!cat.is_wet(), "Cat is wet (see below %s)!",
* ot::PrintMsg(cat, "cat"));
* @endcode
*/
template<typename T>
const char *PrintMsg(const T& object, const char *message) {
ot__private::ZOTPrinter printer;
fprintf(stderr, ANSI_HRED"---- PRINTING %s ----"ANSI_CLEAR"\n", message);
printer.Doit(object, stderr);
return message;
}
/**
* Finds the number of bytes required to freeze an object.
*/
template<typename T>
size_t PointerFrozenSize(const T& obj) {
ot__private::ZOTFrozenSizeCalculator calc;
calc.Doit(obj);
return calc.size();
}
/**
* Makes a copy of an object, freezing it for the first time.
*/
template<typename T>
void PointerFreeze(const T& live_object, char *block) {
ot__private::ZOTPointerFreezer freezer;
freezer.Doit(live_object, block);
DEBUG_SAME_INT(freezer.size(), ot::PointerFrozenSize(live_object));
}
/**
* Takes an object that is laid out serially, and adjusts all its pointers
* so that they are normalized to zero.
*/
template<typename T>
void PointerRefreeze(T* obj) {
ot__private::ZOTPointerRelocator fixer;
fixer.Doit<T>(
0, -mem::PointerAbsoluteAddress(obj),
reinterpret_cast<T*>(obj));
}
/**
* Takes an object that is laid out serially, and adjusts all its pointers
* so that they are normalized to zero.
*
* This assumes that "dest" is an object that is laid out serially, but
* all its pointers are as if it had been copied from src. This is used
* for reading from an existing cache -- the pointers are fixed in a
* temporary buffer rather than in the cache, so that other threads do not
* experience any negative side effects.
*/
template<typename T>
void PointerRefreeze(const T* src, char* dest) {
ot__private::ZOTPointerRelocator fixer;
fixer.Doit<T>(
mem::PointerDiff(dest, src), -mem::PointerAbsoluteAddress(src),
reinterpret_cast<T*>(dest));
}
/**
* Takes an object that is laid out serially with all its pointers
* normalized to zero, and makes all the pointers live again.
*
* Note that this is the function which calls the postprocess function
* (OT_FIX)!
*/
template<typename T>
T* PointerThaw(char *block) {
ot__private::ZOTPointerThawer fixer;
return fixer.Doit<T>(
mem::PointerAbsoluteAddress(block),
block);
}
/**
* Relocates an object from a previous location to a new location.
*
* Call this to fix pointers after swapping or memcopying an object.
*/
template<typename T>
void PointerRelocate(const char *old_location, char *new_location) {
ot__private::ZOTPointerRelocator fixer;
fixer.Doit<T>(
mem::PointerDiff(new_location, old_location),
mem::PointerDiff(new_location, old_location),
reinterpret_cast<T*>(new_location));
}
/**
* Deep-copy initializer for OT-compliant classes.
*/
template<typename T>
void Copy(const T& src, T* dest) {
new(dest)T(src);
}
};
#endif