cgv
Namespace cgv::base

Base Hierarchy

Most toolkits and frameworks provide a common base class from which all classes are derived that can use features of the toolkit or framework. In the cgv framework this class is simply called "base" and lives in the namespace cgv::base. The base class provides support for

  • reference counting of instances
  • type casts
  • self reflection of the members and methods and access to properties
  • registration by a plugin.

Careful: Although multiple inheritance is used heavily in the cgv framework, it is not allowed to inherit base more than one time, what would lead to object schizophrenia. C++ does not allow to enforce this constraint, which therefore lies in the responsibility of the programmer. Please be aware of that!

Besides the base class, the following derived classes extend its functionality:

  • named ... support for a name stored for each instance
  • node ... extends the named by a pointer to a parent instance
  • group ... extends node by methods that allow to organize a list of child instances

Theses classes already provide the functionality to construct doubly linked tree structures and singly linked acyclic graph structures.

The base class has two more methods that can be used by derived classes in an arbitrary way:

  • update() ... for example used by gui classes to post a redraw
  • get_user_data() ... for example by a gui implementation with a specific toolkit, returns the toolkit specific gui element instance

Reference Counting of Instances

Dynamic memory management with malloc/free and new/delete is quite error prone in C and C++. Frequently memory leaks are caused by instances that are allocated but never deallocated again. To circumvent this problem smart pointers have been proposed that implement the concept of reference counting. Here a counter is attached to each allocated instance, which counts the number of pointers that reference the instance. The counter is incremented when a new pointer references the instance and decremented if the reference is removed. As soon as the counter is decreased to zero the instance is removed as it cannot be referenced anymore. Smart pointers are implemented in C++ as template classes that overload (re)implement the constructors, destructor and assignment operator such that reference counting is done automatically.

There are two choices of where to store the reference counter. Either directly as member of the instance or in an additionally allocated memory block. The first approach is faster and more memory efficent, but has the prerequisite that each instance provides a reference counter and that the access to the reference counter is provided to the smart pointer. In the cgv framework both reference counting approaches are supported. The first and more efficient one can be used by deriving a class from cgv::data::ref_counted, which simply adds a reference counter to the members of the class. The smart pointers for both approaches are implemented in the cgv::data::ref_ptr template, which takes the instance type as first template argument and a boolean parameter as second argument. The boolean parameter tells, whether the instance type is derived from cgv::data::ref_counted or not.

The base class is also derived from cgv::data::ref_counted such that reference counting is the default approach for instances derived from base. Therefore all headers corresponding to the types in the base hierarchy contain a declaration of the corresponding smart pointer type, i.e. base_ptr, node_ptr, etc. Please always use these types to declare pointers to instances of types from the base hierarchy.

Type Casts

In C++ there is a very important type cast denoted dynamic_cast which attempts to cast an instance of type A to type B. If the instance is not derived from B, the dynamic type cast returns a null pointer. The common usage of the dynamic type cast operator is to perform casts to derived classes or to check whether an instance implements a given interface. In the cgv framework a lot of functionality works on base_ptrs. For traversal of the tree / graph structures one often needs to know whether a base instance is actually a node or even group and has a parent or even children. Furthermore, the gui driver needs to know from an instance whether it provides a GUI or not. This is done deriving a class from for example node and from the interface class cgv::gui::provider. The GUI driver can then use the dynamic type cast to cgv::gui::provider to check whether an instance provides a GUI.

The base class provides special methods to simplify the dynamic type casts:

  • get_named() ... use dynamic type cast to cast to named_ptr
  • get_node() ... use dynamic type cast to cast to node_ptr
  • get_group() ... use dynamic type cast to cast to group_ptr
  • cast<T>() ... use dynamic type cast to cast to type cgv::data::ref_ptr<T>
  • get_interface<T>() ... use dynamic type cast to cast to interface type T*, note that reference counting cannot be supported anymore as interface types are not derived from base.
  • get_const_interface<T>() ... use dynamic type cast to cast to interface type const T*, note that reference counting cannot be supported anymore as interface types are not derived from base.

Property Interface

In addition to the self reflection functionality a more general property interface is provided by the base class. Properties can simply be class members or more complex configurations that influence more than one class member. Properties are accessed by their names and have only the basic types bool, int (8, 16, 32 or 64 bit), float (32 or 64 bit) or string. Automatic conversion rules between these types are provided such that each property can be queried in each of the available types.

The following methods can be used to access properties:

  • get<T>()... return a property in the type specified as template argument
  • set()... set the property from a value whose type is automatically derived from the template parameter
  • is_property()... check if property of given name exists
  • multi_set()... set several properties from through a string of the form "name1=value1;name2=value2;..."

The programmer has to overload the following methods in derived classes to provide access to properties:

  • base::get_property_declarations() ... return a string containing a semicolon separated list of property declarations.
  • base::get_void() ... retrieve a property value and store it in a value pointed to by the passed void pointer
  • base::set_void() ... set a property from a value pointed to by the passed void pointer

The class cgv::type::variant can be used to access the arguments which are passed as arbitrary types. The default implementation of the property interface uses the method self_reflect() to declare, get and set properties. Example implementations are found in the examples plugin for example in the header plugins/examples/simple_cube_8.h.

Registration by a Plugin

Instances of the classes derived from base can be published to the application and to previously registered listeners by plugins through the registration facility describe in section Registration Mechanism for Plugins. The following two methods allow an instance to react to registration and unregistration:

  • on_register() ... called by registration facility after instance has been registered at all listeners.
  • unregister() ... called by registration facility after instance has been unregistered completely

Registration Mechanism for Plugins

Plugins are compiled code modules that can be loaded at runtime. They are realized in C++ via shared libraries / dlls. Plugins typically contain implementations of interface classes defined in the cgv library such as specializations of image readers. Furthermore, plugins can contain resource files such as icons and images.

The most important task of a plugin is to tell the application by whom it is loaded, which that loads the plugin.

Plugins in cgv use the registration functionality implemented in cgv/base/register.h and cgv/base/register.cxx to provide

  • instances of implemented class,
  • factories for instances
  • test functions
  • registration listeners,
  • drivers (for example for the gui),
  • servers (for example for fonts or triggers) and
  • resource files such as image files, which are embedded into an executable of an application or into a plugin.
  • resource files can be embedded in binary format into an application or plugin by converting it with the res_prep tool to a cxx-file that can be compiled. This performs an automatic registration of the resource file, which can be accessed through the functions defined in the header cgv/data/import.h (see function cgv::base::register_resource_file). For resource files with the extensions "*.bmp;*.jpg;*.jpeg;*.png;*.tif;*.tiff" (defined in the file make/vs/cgv_rules.rules) the files only need to be present in the source directory and the makefile generation facility does the rest.

Except of resource files all other registration entity must be of a class type that inherits cgv::base::base exactly once. Specialized registration entities are marked as such by deriving them also from one of the classes (all declared in cgv/base/register.h):

Registration can be done for example in the main function of an application with the function cgv::base::register_object(object_ptr, options) taking two parameters - the to be registered object and an option string. The option string is used to steer where the object should be registered to and to allow specification of arbitrary registration parameters. For example when registering a factory, one can specify a menu text and a shortcut that are used by the standard viewer to integrate the factory into the menu structure.

When calling the cgv::base::register_object function, the object and option string are passed to all previously registered registration listeners with the method cgv::base::registration_listener::register_object(object,option). After registration the cgv::base::base::on_register() method of the registered object is called. This gives the object the opportunity to initialize itself knowing that it has been integrated into the gui and rendering structures.

As the programmer cannot know which registration listeners keep reference counted pointers to a registered object, the function cgv::base::unregister_object is provided. Again all registered listeners are notified by their unregister_object method and afterwards the unregister() method of the object itself is called. The listeners must make sure to remove all reference counted pointers onto the object passed to their unregister_object() method. The object itself would unregister all objects created by itself in its unregister() method. Typically, unregistration of an object will cause it and all its dependent objects to be destroyed.

The dynamic version of the cgv_viewer allows to load plugins in form of shared libraries / dlls based on parsing the command line arguments or configuation files. The viewer uses the function cgv::base::process_command_line_args to process all commands specified on the command line and recursively in configuration files. As the main functions in shared libraries / dlls are not standardized, the register_object() function cannot be used in plugins to for registration. Instead one declares static instances of helper classes whose constructors are called when loading the shared library. The registration is then performed in the constructor of the helper class. The following helper classes are provided in cgv/base/register.h:

  • cgv::base::object_registration<T> ... registers an instance created with the default constructor. The constructor of the helper class also allows to specify an options string,
  • cgv::base::object_registration_1<T,CA> ... same as above but uses a constructor with one argument of type CA,
  • cgv::base::object_registration_2<T,CA> ... same as above but uses a constructor with two arguments of type CA1 and CA2,
  • cgv::base::factory_registration<T> ... registers a default implementation of a factory class that allows to create new instances during runtime. The constructor of the helper class has additional parameters for the type name of the created instances, whether the factory can generate only one instances at a time and the options for factory as well as created instance registration.
  • cgv::base::factory_registration_1<T,CA> ... see above
  • cgv::base::factory_registration_2<T,CA1,CA2> ... see above
  • cgv::base::resource_file_registration ... registeres a resource file, what is typically only used in source files automatically generated with the res_prep tool for resource files

During loading a shared library the registration is automatically disabled with the function cgv::base::disable_registration(). This is important as the construction of instances that demand for the loading of another shared library can cause a deadlock of the application. Instead in the disabled registration all registration events are cached and executed after loading of the shared library has been completed. The the cgv::base::enable_registration() function is called again and the objects are registered in the order server, driver, listerener and all the rest. Resource file registration on the other hand is unproblematic and not delayed. One can turn off the discarding of the registration events with the function cgv::base::disable_registration_event_cleanup(). This gives listeners of plugins that are read later access to previously registered objects and is the default in the standard cgv_viewer application.

Finally, one can turn on permanent registration by the function cgv::base::enable_permanent_registration() in order to keep a list of all registered objects. This list gives configuration files access to all registered objects and allows to set their members at program start. Also this option is enabled in the standard cgv_viewer application.

Registration Example

Suppose you have implemented a class picker in your plugin and want to register a new instance of type picker when your plugin is loaded. Then you simply add the following line to the cxx file where you implemented the picker class:

#include <cgv/base/register.h>

The argument to the picker_registration constructor is the option string passed to the register_object function and can be used to select a specific listener. For example if you have several windows that can integrate the gui of registered objects, you can select the parent window for your object by specifying the name of the parent window in then argument to the object registration contructor. If an empty string is specified, all listeners will register the object.

If you want to register a factory that can generate picker instances you use the following line instead:

#include <cgv/base/register.h>
cgv::base::factory_registration<picker> picker_registration("picker", "menu_text=\"new/picker\";shortcut='P'");

This will add an entry into the "new" submenu of the main menu and allow you to create pickers with the <Ctrl-P> shortcut. If you want to allow only the creation of one instance of picker, the following line is your choice:

#include <cgv/base/register.h>
cgv::base::factory_registration<picker> picker_registration("picker", "menu_text=\"new/picker\";shortcut='P'", true);

In this signleton case pressing the shortcut <Ctrl-P> several times hides and shows your picker instance.

Finally, to add your own registration listener you use the object_registration helper class on a class derived by cgv::base::base and cgv::base::registration_listener.

cgv::base::object_registration
convenience class to register an object of the given class type
Definition: register.h:157
cgv::base::factory_registration
convenience class to register a factory of the given class type
Definition: register.h:299