ObjectStore C++ Advanced API User Guide

Chapter 7

Metaobject Protocol

The information about metaobject protocol (MOP) is organized in the following manner:

Metaobject Protocol (MOP) Overview

The ObjectStore metaobject protocol (MOP) is a library of classes that allow you to access ObjectStore schema information. Schema information for ObjectStore databases and applications is stored in the form of objects that represent C++ types. These objects are actually instances of ObjectStore metatypes, so called because they are types whose instances represent types. Every object representing a type is an instance of a subtype of the metatype os_type. For example, objects representing classes (as opposed to built-in types like int) are instances of os_class_type, which is derived from os_type. (See the hierarchy diagram in The Metatype Hierarchy).

There are several classes in the metaobject protocol whose instances represent schema objects other than types, such as os_base_class and os_member and its subtypes. These auxiliary classes are part of the metaobject protocol (MOP) but are not metatypes and are not, therefore, part of the metatype hierarchy. Descriptions of these auxiliary classes begin on page 206.

Besides telling you how to perform run-time read access to ObjectStore application, compilation, and database schemas, this chapter explains how to create classes dynamically and add them to ObjectStore database schemas.

MOP Header Files

Programs using the metaobject protocol must include <ostore/ostore.hh>, followed by <ostore/coll.hh> (if collection is being used), followed by <ostore/mop.hh>.

Attributes of MOP Classes

It is useful to think of classes in the metaobject protocol as having attributes, that is, pieces of abstract state that you can access using create, get, and set functions.

You initialize an attribute with a create function. You perform read access on an attribute with a get function. And you update an attribute with a set function. Most of the functions in the metaobject protocol are create, get, or set functions, members of the class whose state they access.

Attributes of
os_class_type
Consider for example the class os_class_type, whose instances represent C++ classes. Here is a diagram showing its attributes.

Each arrow represents an attribute, and points to the type of values the attribute has. The arrow's label is the name of the corresponding attribute. The attribute name, for example, is string valued. Double arrows indicate a multivalued attribute. For example, the attribute members has zero or more os_members as its values. These values, for a given instance of os_class_type, each represent a member of the class represented by the given os_class_type.

Key to arrow shades


The shade of the arrows used throughout this chapter indicates what type of access to the corresponding attribute is supported. The darkest arrows correspond to attributes with create, get, and set functions. The arrows of medium shade correspond to attributes with get and set functions. And the lightest arrows correspond to attributes with get functions only.

create() function for MOP classes
The create function (or functions) for each class is called create(), and is used to create instances of the class. The get and set functions for each attribute have names based on the attribute name, as follows. For attributes that are not Boolean valued, the functions are

      get_ attribute-name()
and

      set_ attribute-name()
For Boolean-valued attributes, the get and set functions are, respectively,

       attribute-name()
and

      set_ attribute-name()

Schema Read Access Compared to Schema Write Access

Many applications that use the MOP perform only read access on schema information. A browser application, for example, might allow viewing of schema information, but never perform updates to schemas. Such an application would not use create or set functions.

Schema Read Access

Read access to schema information always starts in one of two ways:

In either case, a pointer to a const object is returned. Other schema objects are the result of performing get functions on these initial const objects (and the result of performing get functions on these results, and so on). For class-valued attributes, these get functions, in turn, return pointers or references to const objects. Since set functions only take non-const this arguments, direct updates to application, compilation, and database schemas are prevented (assuming you use no explicit casts to non-const).

Schema Write Access

To update a database schema, you must first construct, in the transient schema, the classes you want to modify or add to the database schema (see The Transient Schema). Then you perform installation or evolution on the schema you want to update, specifying the classes in the transient schema as input to the installation or evolution process (see Schema Installation and Evolution Using MOP).

Creating a transient schema
Creating classes in the transient schema always starts in one of two ways:

In either case, a reference or pointer to a non-const object is returned. The get functions, when performed on non-const objects, will return non-const objects. This is because, except for get functions that return built-in types, get functions come in pairs:

or

Schema Consistency Requirements

Constraints on database schemas
Schema objects in a database schema must meet certain consistency requirements that can be (temporarily) violated by objects in the transient schema. For example, in a database schema, if an os_class_type, c, has an os_member, m, as a value of the attribute members, then m must have c as the value for its attribute defining_class.

Flexibility of database schemas
To allow flexibility in the construction of schemas, the transient schema has no such restrictions. That way you can, for example, create an os_member without at first specifying the class that defines it. However, before using classes in the transient schema as input to installation or evolution, you should ensure that the consistency requirements are met. ObjectStore checks such an input for consistency before modifying a database schema, and signals err_mop if any requirements are not met.

In the MOP interface, pointer arguments to create and set functions generally indicate that 0 is an acceptable initial value for the argument's corresponding attribute; if 0 is not an acceptable value, a reference (&) argument is used instead of a pointer argument. But note that a nonnull value for the attribute might have to be supplied before installation or evolution, in order to meet the consistency requirements.

For an attribute that must have a nonnull value in order to meet the consistency requirements, the get functions return a reference type (unless they return a built-in type). When performed on an object that does not yet have a nonnull value for the attribute, such a get function returns an unspecified object (see The is_unspecified() function).

For an attribute that might have a null value and still meet the consistency requirements, the get functions return a pointer type (unless they return a built-in type).

Retrieving an Object Representing the Type of a Given Object

The type_at() Function

You can retrieve an instance of os_type that represents the type of a given persistent object by passing the object's address to the static member function os_type::type_at(). This function is declared as follows:

type_at() declaration
      static const os_type *type_at(const void *p) ;
Note that p must point to persistent memory.

For pointers that point to the beginning of more than one object (that is, pointers that point to co-located objects), this function returns the type of the outermost object. For example, consider a pointer typed as a part* that points to a direct instance of mechanical_part, derived from part; and suppose that part has no base types. Passing this pointer to type_at() results in an os_type representing the class mechanical_part - unless the object is embedded as a data-member value in the initial bytes of some other object, in which case the function would return an os_type representing this other object.

Example: type_at()
Here is an example of the function's use:

      f () {
      part *p = ... ;
      . . . 
      const os_type *t = os_type::type_at(p);
      . . . 
      }

The type_containing() Function

You can also retrieve the type of the outermost object containing a given persistent object using os_type::type_containing().

type_containing() declaration
            static const os_type *type_containing(
                  const void *p,
                  const void*& p_container,
                  os_unsigned_int32& element_count            
            );
Unlike type_at(), the object whose type is returned does not necessarily begin at the specified address; that is, the argument p might point to a subobject or data-member value embedded in the middle of the object whose type is returned.

The address of the object whose type is returned, the outermost object containing the specified object, is referred to by p_container when the function returns.

Arrays are handled specially by type_containing(). If the outermost containing object is an array, the element type of the array is returned, and element_count is set to refer to the array's size. If the containing object is not an array, element_count is set to 1.

Retrieving Objects Representing Classes in a Schema

ObjectStore schemas are represented by instances of classes derived from os_schema.

Instances of these classes represent ObjectStore schemas.

Retrieving database schema
You can retrieve the compilation, application, or database schema in a given database with the static member functions os_comp_schema::get(), os_app_schema::get(), and os_database_schema::get().

      static const os_app_schema &os_app_schema::get(
                  const database&);
      static const os_comp_schema &os_comp_schema::get(
                  const database&);
      static const os_database_schema &os_database_schema::get(
                  const database&);
Retrieving application schema
You can also retrieve the application schema for the current application with

      static const os_app_schema &os_app_schema::get();
Retrieving classes in a given schema
You can retrieve objects representing the classes in a given schema with os_schema::get_classes().

      os_Collection<const os_class_type*> 
os_schema::get_classes() const;
You can also retrieve the class with a given name in a given schema with os_schema::find_type().

      const os_type *os_schema::find_type(const char*) const;
Retrieving class types
You can retrieve the os_class_types in a given compilation schema this way (the examples in this chapter use the parameterized collection classes; if your compiler does not support class templates, use the nonparameterized collection classes instead):

      f () {
            os_database *my_db =
                  os_database::open("/foo/bar/comp_schema");
            os_Collection<const os_class_type*> the_classes =
                  os_comp_schema::get(*my_db).get_classes();
      . . . 
      }
You can retrieve the os_class_types in a given application schema this way:

      f () {
            os_database *my_db =
                  os_database::open("/foo/bar/app_schema");
            os_Collection<const os_class_type*> the_classes =
                  os_app_schema::get(*my_db).get_classes();
      . . . 
      }
You can retrieve the types in a database schema this way:

      f () {
            os_database *my_db = os_database::open("/foo/bar/db1");
            os_Collection<const os_class_type*> the_classes =
                  os_database_schema::get(*my_db).get_classes();
            . . . 
      }
Finally, you can retrieve the class with a given name in a given schema this way:

      f () {
            os_database *my_db = os_database::open("/foo/bar/db1");
            const os_type *the_type =
                  os_database_schema::get(*my_db).find_type("part");
            . . . 
      }
See the entries for the classes os_schema, os_comp_schema, os_app_schema, and os_database_schema in the ObjectStore C++ API Reference.

The Transient Schema

The metaobject protocol provides the ability to programmatically update a database's schema. All modification of database schemas, however, is mediated by the transient schema. As described earlier, to update a database schema, you must first construct, in the transient schema, the classes you want to modify or add to the database schema. Then you perform installation or evolution on the database schema, specifying the classes in the transient schema as input to the installation or evolution process.

Initializing the Transient Schema with initialize()

Before using the transient schema, you must always call os_mop::initialize().

      static void initialize() ;
Recall that creating classes in the transient schema always starts in one of two ways:

Copying into the Transient Schema with copy_classes()

To copy classes from a schema into the transient schema, you use the static member function os_mop::copy_classes().

      static void copy_classes(
            const os_schema &schema, 
            os_Set<const os_class_type*> &classes
      ) ;
The first argument is the schema containing the classes to be copied, and the second argument is a set of pointers to the classes to be copied. So before calling this function, you must perform these steps:

Preparation for the copy operation
  1. First, retrieve the schema from which you want to copy classes. For example the following retrieves the application schema for the current application:

            const os_app_schema &the_app_schema =
                  os_app_schema::get() ;
  1. Next, before calling copy_classes(), you must create a set to hold the pointers to the classes you want to copy, as in

            os_Set<const os_class_type*>
                  to_be_copied_to_transient_schema ;
  1. Then, for each class you want to copy, follow these steps:

                  const os_type *the_const_type_os_collection =
                        the_app_schema.find_type("os_collection") ;


                  if (!the_const_type_os_collection)
                        error("Could not find the type os_collection in \
                        the app schema") ;


                  const os_class_type *the_const_class_os_collection =
                        *the_const_type_os_collection ;


                  to_be_copied_to_transient_schema |= 
                        the_const_class_os_collection ;

  1. Once this is done for each class you want to copy, you are ready to call copy_classes():

            os_mop::copy_classes(
                  the_app_schema,
                  to_be_copied_to_transient_schema
            ) ;

Looking Up a Class in the Transient Schema with find_type()

After you have copied a class into the transient schema, you can look it up by name in the transient schema with os_mop::find_type().

      static os_type *find_type(const char *name) ;
Notice that os_mop::find_type(), unlike os_schema::find_type(), returns a pointer to a non-const os_type. This means you can modify the os_type by performing set functions on it, and you can retrieve other modifiable objects by performing get functions on it. You can also make other objects in the transient schema refer to it.

For example, if you do

      os_type *the_non_const_type_os_collection =
            os_mop::find_type("os_collection") ;
You can then create a collection-valued data member (see The Class os_member_variable):

      assert (the_non_const_type_os_collection) ;
      os_member_variable &new_member =
            os_member_variable::create(
                  member_name,
                  the_non_const_type_os_collection
            ) ;

Schema Installation and Evolution Using MOP

os_database_schema::install()
To install classes from the transient schema into a database schema, you use the function os_database_schema::install().

      void install(os_schema &new_schema) ;
os_mop::get_transient_schema()
The actual argument should be a reference to the transient schema. You can retrieve such a reference with os_mop::get_transient_schema().

      static os_schema &get_transient_schema() ;
os_database_schema::get_for_update()
The this argument is a pointer to the schema you want to modify. Notice that install() function cannot take a const this argument. So you cannot use os_database_schema::get() to retrieve the schema to be modified. Instead, you use os_database_schema::get_for_update(), which takes a const database& argument.

      static os_database_schema &get_for_update(
const os_database&)
Example: schema install
So a call to install might look like

      os_database_schema::get_for_update(*db).install(
            os_mop::get_transient_schema()
      ) ;
Nondefault behavior
To specify nondefault installation behavior, you can use the alternate overloading of os_database_schema::install that takes an os_schema_install_options argument.

      void install (os_schema &new_schema, 
                  os_schema_install_options
      ) ;
See os_schema_install_options in Chapter 2, Class Library, of the ObjectStore C++ API Reference for a description of the specific installation options available.

The os_schema_install_options allows you to control whether member functions are copied into the database schema during installation. The default behavior is to not copy member functions.

os_schema_evolution::evolve()
If you want to modify a database schema in a way that requires evolution, you should use os_schema_evolution::evolve() rather than install().

      static void evolve(
            const char *workdb_name,
            const char*database_name,
            os_schema &new_schema
      ) ;
The new_schema argument should be the transient schema. So a call to evolve might look like

      static void evolve(
            "/example/workdb",
            "/example/partsdb",
            os_mop::get_transient_schema()
      ) ;

The Metatype Hierarchy

All the types in the C++ type system can be divided into the following categories: class types, integer types, real types, enumeration types, array types, pointer types, function types, and the type void. For each of these categories, there is a subclass of os_type in the metatype hierarchy.

os_instantiated_class_type
The class os_instantiated_class_type, derived from os_class_type, represents the category of template class instantiations. (The class os_Collection<part*>, for example, is an instantiation of the template class os_Collection.)

How different types are represented
Pointer types, such as void* and part*, are represented by direct instances of os_pointer_type. Reference types, such as part&, are represented by instances of os_reference_type, derived from os_pointer_type. Pointer-to-member types are represented by instances of os_pointer_to_member_type, also derived from os_pointer_type.

Types with const or volatile specifiers are represented by instances of os_anonymous_indirect_type, and typedefs are represented by instances of os_named_indirect_type.

All the classes in the metatype hierarchy are documented in Chapter 2, Class Library, of the ObjectStore C++ API Reference. Note that these are not all the types in the metaobject protocol. There are several classes in the metaobject protocol whose instances represent schema objects other than types, such as os_base_class (see The Class os_base_class), and os_member and its subtypes (see The Class os_member). The rest of this chapter contains a section on each class in the protocol.

The Class os_type

Below is a diagram showing the attributes of the class os_type. Each arrow represents an attribute and points to its value type. The darkest arrows have corresponding set functions, get functions, and create arguments. The medium arrows have corresponding set functions and get functions. The lightest arrows have corresponding get functions only.

See os_type in Chapter 2, Class Library, of the ObjectStore C++ API Reference for a complete description of this class.

Attributes of os_type


Create Functions

This class defines no create functions. You always create an instance of os_type using a create function defined by one of the subtypes of os_type.

The kind Attribute

The kind of an os_type is an enumerator indicating what kind of type is represented by the os_type. The enumerators are

kind enumerators
      os_type::Void
      os_type::Named_indirect
      os_type::Anonymous_indirect
      os_type::Char
      os_type::Unsigned_char
      os_type::Signed_char
      os_type::Unsigned_short
      os_type::Signed_short
      os_type::Integer
      os_type::Unsigned_integer
      os_type::Signed_long
      os_type::Unsigned_long
      os_type::Float
      os_type::Double
      os_type::Long_double
      os_type::Pointer
      os_type::Reference
      os_type::Pointer_to_member
      os_type::Array
      os_type::Class
      os_type::Instantiated_class
      os_type::Enum
      os_type::Function
      os_type::Type
Finding the kind of an os_type with get_kind()
The function os_type::get_kind() returns the kind of the specified os_type.

      os_type_kind get_kind() const ;
Given an object typed as an os_type, you can use get_kind() to determine the subtype of os_type that the object is an instance of. Then you can convert the object to that subtype using the type-safe conversion operators. See Type-Safe Conversion Operators.

Retrieving the kind_string Attribute

There is a static member function that returns the name of a given os_type_kind:

      static const char *get_kind_string(os_type_kind) ;
For example os_type::get_kind_string(os_type::Class) returns Class.

Retrieving the string Attribute

The function os_type::get_string() returns a new string containing an expression designating the specified type (like part or const part).

      char *get_string() const ;
Note that this function allocates the returned string on the heap, so you should delete it when it is no longer needed.

Determining an os_type's Type and Status

The is_const() function
The function os_type::is_const() returns nonzero if the specified os_type is an os_anonymous_indirect_type representing a const type (such as const char*). Returns 0 otherwise.

      os_boolean is_const() const ;
The is_volatile() function
The function os_type::is_volatile() returns nonzero if the specified os_type is an os_anonymous_indirect_type representing a volatile type (such as volatile short). Returns 0 otherwise.

      os_boolean is_volatile() const ;
The is_integral_type() function
The function os_type::is_integral_type() returns nonzero if the specified os_type is an instance of os_integral_type (such as one representing the type unsigned int). Returns 0 otherwise.

      os_boolean is_integral_type() const ;
The is_real_type() function
The function os_type::is_real_type() returns nonzero if the specified os_type is an instance of os_real_type (such as one representing the type long double). Returns 0 otherwise.

      os_boolean is_real_type() const ;
The enclosing_class() function
If a class's definition is nested within that of another class, this other class is the enclosing class of the nested class.

There is a pair of get functions, get_enclosing_class(), one taking const this and returning a const os_class_type*, and one taking non-const this and returning an os_class_type*.

      const os_class_type *get_enclosing_class() const ;
      os_class_type *get_enclosing_class() ;
The function returns 0 if there is no enclosing class.

The strip_indirect_types() function
There are also functions, os_type::strip_indirect_types(), declared

      const os_type &strip_indirect_types() const ;
      os_type &strip_indirect_types() ;
For types with const or volatile specifiers, this function returns the type being specified as const or volatile. For example, if the specified os_type represents the type const int, strip_indirect_types() will return an os_type representing the type int. If the specified os_type represents the type char const * const, strip_indirect_types() will return an os_type representing the type char const *.

For typedefs, this function returns the original type for which the typedef is an alias.

This function calls itself recursively, until the result is not an os_indirect_type. So, for example, consider an os_named_indirect_type representing

      typedef const part const_part
The result of applying strip_indirect_types() to this is an os_class_type representing the class part (not an os_anonymous_indirect_type representing const part - which would be the result of os_indirect_type::get_target_type()).

The is_unspecified() function
Some os_type-valued attributes in the metaobject protocol are required to have values in a consistent schema, but might lack values in the transient schema, before schema installation or evolution is performed. The get function for such an attribute returns a reference to an os_type or os_class_type. The fact that a reference rather than a pointer is returned indicates that the value is required in a consistent schema.

In the transient schema, if such an attribute lacks a value (because you have not yet specified it), the get function returns the unspecified type. This is the only os_type for which the following predicate returns nonzero:

      os_boolean is_unspecified() const ;

Type-Safe Conversion Operators

The class os_type also defines conversion operators for converting an os_type& to a reference to any of the subtypes of os_type:

      operator os_void_type&() ;
      operator os_named_indirect_type&() ;
      operator os_anonymous_indirect_type&() ;
      operator os_integral_type&() ;
      operator os_real_type&() ;
      operator os_pointer_type&() ;
      operator os_reference_type&() ;
      operator os_pointer_to_member_type&() ;
      operator os_array_type&() ;
      operator os_class_type&() ;
      operator os_instantiated_class_type&() ;
      operator os_enum_type&() ;
      operator os_function_type&() ;
The existence of these operators allows you to supply an expression of type os_type or os_type& as the actual parameter for a formal parameter of type, for example, os_integral_type& - provided the designated os_type is actually an instance of os_integral_type. If it is not, err_mop_illegal_cast is signaled. Each of these operators is type safe in the sense that err_mop_illegal_cast is always signaled if it is used to perform an inappropriate conversion.

There are also conversion operators for converting a const os_type& to a const reference to any of the subtypes of os_type:

      operator const os_void_type&() const ;
      operator const os_named_indirect_type&() const ;
      operator const os_anonymous_indirect_type&() const ;
      operator const os_integral_type&() const ;
      operator const os_real_type&() const ;
      operator const os_pointer_type&() const ;
      operator const os_reference_type&() const ;
      operator const os_pointer_to_member_type&() const ;
      operator const os_array_type&() const ;
      operator const os_class_type&() const ;
      operator const os_instantiated_class_type&() const ;
      operator const os_enum_type&() const ;
      operator const os_function_type&() const ;

The Class os_integral_type

Instances of this class represent one of the following types:

      int
      unsigned int
      short
      unsigned short
      long
      unsigned long
      char
      signed char
      unsigned char
See os_integral_type in Chapter 2, Class Library, of the ObjectStore C++ API Reference for a complete description of this class.

Attribute of
os_integral_type
This class defines one attribute, is_signed:

Create Functions

This class has several create functions for the various kinds of integer types.

      static os_integral_type &create_signed_char() ;
      static os_integral_type &create_unsigned_char() ;
      static os_integral_type &create_defaulted_char(
                  os_boolean signed) ; 
      static os_integral_type &create_short(os_boolean signed) ;
      static os_integral_type &create_int(os_boolean signed) ;
      static os_integral_type &create_long(os_boolean signed) ;

Determining a Signed Type with is_signed()

The following function can be used to determine if the specified type is signed:

      os_boolean is_signed() const ;

The Class os_real_type

Instances of this class represent one of the following types:

      float
      double
      long double
See os_real_type in Chapter 2, Class Library, of the ObjectStore C++ API Reference for a complete description of this class.

Create Functions

This class has three create functions, one for each of the real types listed above.

      static os_real_type &create_float() ;
      static os_real_type &create_double() ;
      static os_real_type &create_long_double() ;

The Class os_class_type

An instance of os_class_type represents a C++ class. In addition to classes declared with the keyword class, structs and unions are also represented by instances of os_class_type.

See os_class_type in Chapter 2, Class Library, of the ObjectStore C++ API Reference for a complete description of this class.

Attributes of
os_class_type
Below is a diagram showing some of the important pieces of abstract state associated with the class os_class_type. Each arrow represents a piece of state and points to its value type. In the case of double arrows, the value type is an os_List, and the type pointed to by the arrow is the element type of the os_List.

Create Functions

This class has two create functions:

      static os_class_type &create ( const char *name ) ;
The argument specifies the initial value for the name attribute. The initial values for the remaining attributes are as follows:
AttributeValue
base_classesempty os_List<os_base_class*>
membersempty os_List<os_member*>
defines_virtual_functions0
class_kindos_class_type::Class
defines_get_os_typespec_function0
is_template_class0
is_persistent0
is_forward_definition1

The second overloading allows specification of more attribute initial values:

      static os_class_type &create (
            const char *name ,
            const os_List<os_base_class*> &base_classes ,
            const os_List<os_member*> &members ,
            os_boolean defines_virtual_functions
      ) ;
The arguments specify the initial values for the attributes name, base_classes, members, and defines_virtual_functions. The initial values for the remaining attributes are as follows:
AttributeValue
class_kindos_class_type::Class
defines_get_os_typespec_function0
is_template_class0
is_persistent0
is_forward_definition0

The name Attribute

Getting the attribute
The name of the class represented by a given instance of os_class_type is returned by the following function:

      const char *get_name() const ;
The returned value points to the beginning of the persistent character array holding the class's name.

Setting the attribute
You can specify a new character string to serve as a class's name with the following function:

      void set_name(const char*) ;

The class_kind Attribute

The value of class_kind for a given os_class_type is an enumerator that indicates the kind of class specified. The enumerators are:

class_kind enumerators
Getting the attribute
You can get the class_kind with

      os_unsigned_int32 get_class_kind() const ;
Setting the attribute
You can set it with

      void set_class_kind(os_unsigned_int32) ;

The members Attribute

With the metaobject protocol, the members (both data members and member functions) of a class are sometimes manipulated as a group, using an ObjectStore list of either type:

Each instance of os_member represents a member of the class (see The Class os_member). The order of elements in the os_List signifies the declaration order of the members.

Getting the attribute
You can retrieve a list of the members of a specified class using the following functions:

      os_List<os_member*> get_members() ;
      os_List<const os_member*> get_members() const ;
These functions signal err_mop_forward_definition if the value of is_forward_definition is nonzero for the specified os_class_type.

Setting the attribute
You can specify the members of a class using

      void set_members(const os_List<os_member*>&) ;
This replaces the members of the specified class with the specified members.

You can also initialize the members of a class with a create function; see Create Functions.

os_base_class Objects

As with members, the os_base_class objects associated with a class are sometimes manipulated as a group. The list has one of the following two types:

Each instance of os_base_class represents the derivation of one class from another (see The Class os_base_class). The order of elements in the os_List signifies the declaration order of the base classes involved.

Retrieving the objects
You can retrieve a list of the os_base_class objects for a specified class using the following functions:

      os_List<os_base_class*> get_base_classes() ;
      os_List<const os_base_class*> get_base_classes() const ;
These functions signal err_mop_forward_definition if the value of is_forward_definition is nonzero for the specified os_class_type.

Specifying the objects
You can specify the os_base_class objects for a class using

      void set_base_classes(const os_List<os_base_class*>&) ;
This replaces the os_base_class objects for the specified class with the specified os_base_class objects.

Initialization
You can also initialize the os_base_class objects for a class with a create function; see Create Functions.

The declares_get_os_typespec_function Function

You can determine if a given class declares a get_os_typespec() member function with

      os_boolean declares_get_os_typespec_function() const ;
which returns nonzero if the class does declare such a member function, and 0 otherwise.

The set_declares_get_os_typespec_function Function

You can specify that a given class declares a get_os_typespec() member function with

      void set_declares_get_os_typespec_function(os_boolean) ;
If you supply 1, the class will declare such a member function; if you supply 0, it will not.

The defines_virtual_functions Attribute

The value of this attribute for a given class is nonzero if and only if the class has a field for a pointer to a virtual function table. This value is never computed based on the functions in members. It is zero by default, and nonzero only if so initialized or set by the user.

When you attempt to install a class in a schema, if a function in members is virtual, defines_virtual_functions must be nonzero, or installation will fail.

Getting the attribute
You can retrieve this value with the following function:

      os_boolean defines_virtual_functions() const ;
Setting the attribute
You can set this attribute with

      void set_defines_virtual_functions(os_boolean) ;
Initialization
You can also initialize defines_virtual_functions with a create function; see Create Functions.

The introduces_virtual_functions Attribute

The value of this attribute for a given class is nonzero if the class defines a virtual function but does not inherit any virtual functions.

Getting the attribute
You can retrieve this value with the following function:

      os_boolean introduces_virtual_functions() const ;
Setting the attribute
You can set this attribute with

      void set_introduces_virtual_functions(os_boolean) ;

The is_forward_definition Attribute

Sometimes a class, C, appears in a schema only as a forward definition, because some other class uses the type C* in its definition (or uses some other pointer type involving C) but a full-fledged definition for C is not required. For such a class, is_forward_definition is nonzero.

Getting the attribute
You can get the value of this attribute with

      os_boolean is_forward_definition() const ;
Setting the attribute
You can set this attribute with

      void set_is_forward_definition(os_boolean) ;

The is_persistent Attribute

In order to be installed into a database schema, a class must either be persistent - that is, have a nonzero (true) value for this attribute - or be reachable from a persistent class. Making a class persistent is similar to marking it with OS_MARK_SCHEMA_TYPE().

Getting the attribute
You can get the value of this attribute with

      os_boolean is_persistent() const ;
Setting the attribute
You can set it with

      void is_persistent(os_boolean) ;

Finding the Nonvirtual Base Class with find_base_class()

The following functions return the os_base_class representing the derivation of this from the nonvirtual base class with the specified name.

      const os_base_class *find_base_class(const char *name) const ;
      os_base_class *find_base_class(const char *name) ;
The functions return 0 if there is no such base class, and signal err_forward_definition if this is a forward definition.

Finding Base Classes from Which this Inherits with
get_allocated_virtual_base_classes()

The get_allocated_virtual_base_classes() function returns base classes from which this inherits:

      os_List<const os_base_class*> 
      get_allocated_virtual_base_classes() const;
      os_List<os_base_class*> get_allocated_virtual_base_classes() ;
The function returns 0 if there are no such base classes, and signals err_forward_definition if this is a forward definition.

Finding Classes from Which this Indirectly Inherits with
get_indirect_virtual_base_classes()

The get_indirect_virtual_base_classes() function returns base classes from which this indirectly and virtually inherits:

      os_List<const os_base_class*> 
      get_indirect_virtual_base_classes() const;
      os_List<os_base_class*> get_indirect_virtual_base_classes() ;
The function returns 0 if there are no such base classes, and signals err_forward_definition if this is a forward definition.

Finding the Name of this with find_member()

The find_member() function returns the member value of this that has the specified name:

      const os_member_variable 
*find_member(const char *name) const ;
os_member_variable *find_member(const char *name) ;
The function returns 0 if there is no such member.

Finding a Containing Object with get_most_derived_class()

The get_most_derived_class() function can be used to determine the object containing a specified data member value, as well as the class of that object:

      static const os_class_type &get_most_derived_class (
            const void *object,
            const void* &most_derived_object
      ) const ;
If object points to the value of a data member for some other object, o, this function returns a reference to the most derived class of which o is an instance. A class, c1, is more derived than another class, c2, if c1 is derived from c2, or derived from a class derived from c2, and so on. most_derived_object is set to the beginning of the instance of the most derived class. There is one exception to this behavior, described below.

If object points to an instance of a class, o, but not to one of its data members (for example, because the memory occupied by the instance begins with a virtual table pointer rather than a data member value), the function returns a reference to the most derived class of which o is an instance. most_derived_object is set to the beginning of the instance of the most derived class. There is one exception to this behavior, described below.

If object does not point to the memory occupied by an instance of a class, most_derived_object is set to 0, and err_mop is signaled. ObjectStore issues an error message like the following:

      <err-0008-0010>Unable to get the most derived class in
      os_class_type::get_most_derived_class() containing the
      address 0x[[some-address]].
Example: get_most_derived_class()
Here is an example:

Class and
function
declarations

      class B {
            public:
                  int ib ;
      } ;
      class D : public B {
            public:
                  int id ;
} ;
      class C {
            public:
                  int ic ;
                  D cm ;
      } ;
      void baz () {
            C* pC = new (db) C;
            D *pD = &pC->cm ;
            int *pic = &pC->ic, *pid = &pC->cm.id, *pib = &pC->cm.ib ;
            . . . 
      }
  
 Function result

Invoking get_most_derived_class() on the pointers pic, pid, and pib has the results shown in the following table:
objectmost_derived_objectos_class_type
picpCC
pidpDD
pibpDD

The exception to the behavior described above can occur when a class-valued data member is collocated with a base class of the class that defines the data member. If a pointer to such a data member (which is also a pointer to such a base class) is passed to get_most_derived_class(), a reference to the value type of the data member is returned, and most_derived_object is set to the same value as object.

Example: class hierarchy
Consider, for example, the following class hierarchy:

Class
definitions

      class C0 {
            public:
                  int i0;
      };
      class B0 {
            public:
                  void f0();
      };
      class B1 : public B0 {
            public:
                  virtual void f1();
                  C0 c0;
      };
      class C1 : public B1 {
            public:
                  static os_typespec* get_os_typespec();
                  int i1;
      };
Some compilers will optimize B0 so that it has zero size in B1 (and C1). This means the class-valued data member c0 is collocated with a base class, B0, of the class, C1, that defines the data member.

Given

      C1 c1;
      C1 * pc1 = & c1;
      B0 * pb0 = (B0 *)pc1;
      C0 * pc0 = & pc1->c0;
the pointers pb0 and pc0 will have the same value, because of this optimization.

Function result

In this case get_most_derived_class() called on the pb0 or pc0 will return a reference to the os_class_type for C0 (the value types of the data member c0) and most_derived_object are set to the same value as object.

If B1 is instead defined as

      class B1 : public B0 {
            public:
                  virtual void f1();
                  int i;
                  C0 c0;
      };
and

      int * pi = & pc1->i;
pb0 and pi have the same value because of the optimization, but the base class, B0, is collocated with an int-valued data member rather than a class-valued data member. get_most_derived_class() called on pb0 or pi returns a pointer to the os_class_type for the class C1 and sets most_derived_object to the same value as pc1.

The Class os_base_class

An instance of os_base_class represents the derivation of one class from another. Such an instance serves to associate the base class with the nature of the derivation (virtual or nonvirtual, and public, private, or protected).

See os_base_class in Chapter 2, Class Library, of the ObjectStore C++ API Reference for a complete description of this class.

Attributes of
os_base_class


Create Functions

This class has one create function:

      static os_base_class &create (
            os_unsigned_int32 access ,
            os_boolean is_virtual ,
            os_class_type *associated_class
      ) ;
Function arguments
The arguments specify the initial values for the attributes access, is_virtual, and class.

To represent the derivation of mechanical_part from part, you might create an os_base_class as follows:

Example: os_base_class
      os_class_type *the_class_part = ...
      os_class_type *the_class_mechanical_part = ...
      . . . 
      os_base_class & a_base = os_base_class::create (
            os_base_class::Public ,
            0,
            os_class_type *the_class_part
      ) ;
      os_List<os_base_class*> bases ;
      bases |= a_base ;
      the_class_mechanical_part->set_base_classes ( bases ) ;

The class Attribute

The class of an os_base_class is the base class for the derivation represented by the os_base_class. For example, suppose mechanical_part is derived from part. Performing get_base_classes() on an os_class_type representing mechanical_part results in a list containing an os_base_class representing the derivation of mechanical_part from part. Performing get_class() on this os_base_class object results in an os_class_type representing part.

Getting the attribute
You can get the class of a given os_base_class with the following functions:

      const os_class_type &get_class() const ;
      os_class_type &get_class() ;
If no class was specified when the os_base_class was created (that is, if the associated class argument to create() was 0), the unspecified type is returned. This is the only os_type for which os_type::is_unspecified() returns nonzero (see The is_unspecified() function).

Setting the attribute
You can set the class of an os_base_class with

      void set_class(os_class_type&) ;
Initialization
You can initialize the class attribute with os_base_class::create().

The access Attribute

The value of the access attribute for a given os_base_class is one of the following enumerators:

      os_base_class::Public
      os_base_class::Private
      os_base_class::Protected
Getting the attribute
You can get the access for an os_base_class with

      os_unsigned_int_32 get_access() const ;
Setting the attribute
You can set the access with

      void set_access(os_unsigned_int_32) ;
Initialization
You can initialize the access attribute with os_base_class::create().

The is_virtual Attribute

The value of is_virtual for a given os_base_class is nonzero if the os_base_class represents a virtual derivation, and is 0 otherwise. The following function returns the value of the is_virtual attribute:

Getting the attribute
      os_boolean is_virtual() const ;
Setting the attribute
You can set this attribute with

      void set_is_virtual(os_boolean) ;

The Class os_member

An instance of os_member represents a data member or member function.

See os_member in Chapter 2, Class Library, of the ObjectStore C++ API Reference for a complete description of this class.

Some member functions of
os_member


Class os_member and its subclasses
This class has no direct instances. Every instance of os_member is a direct instance of one of the subclasses of os_member.

Create Functions

Since this class has no direct instances, it has no create function. You create an instance of os_member with a create function for one of the subclasses of os_member.

The access Attribute

Attribute enumerators
The access of a given os_member is one of the following enumerators:

      os_member::Public
      os_member::Private
      os_member::Protected
Getting the attribute
You can get the access of an os_member with

      os_unsigned_int32 get_access() const ;
Setting the attribute
You can set the access of an os_member with

      void set_access(os_unsigned_int32) ;
If the specified os_unsigned_int32 does not have the same value as one of the above enumerators, err_mop is signaled.

Initialization
This attribute is initialized to os_member::Public by the create functions for the subclasses of os_member.

The kind Attribute

Attribute enumerators
The kind of an os_member is one of the following enumerators:
os_member::VariableFor data members
os_member::FunctionFor member functions
os_member::TypeFor nested classes
os_member::Access_modifierFor access declarations
os_member::Field_variableFor bit fields
os_member::RelationshipFor ObjectStore inverse members

Each of these enumerators corresponds to a subclass of os_member.

Initialization
The value of kind for a given os_member is initialized to the enumerator corresponding to the subclass of which the os_member is a direct instance. This initialization is performed by the create function for the subclass.

Getting the attribute
You can get the kind of an os_member with

      os_unsigned_int32 get_kind() const ;

The defining_class Attribute

The defining_class of an os_member is an os_class_type, the class that defines the os_member.

Getting the attribute
You can get the defining class of an os_member with

      const os_class_type &get_defining_class() const ;
      os_class_type &get_defining_class() ;
Initialization
This attribute is always initialized by a create function to a special instance of os_class_type, the unspecified class (see The is_unspecified() function).

Setting the attribute
The defining_class of an os_member is automatically set when os_class_type::set_members() is passed a collection containing a pointer to the os_member. The os_member's defining_class is set to the os_class_type for which set_members() is called.

Type-Safe Conversion Operators

Like the class os_type, os_member defines type-safe conversion operators for converting const os_members to const references to an instance of a subtype.

      operator const os_member_variable&() const ;
      operator const os_field_member_variable&() const ;
      operator const os_relationship_member_variable&() const ;
      operator const os_member_function&() const ;
      operator const os_access_modifier&() const ;
      operator const os_member_type&() const ;
There are also type-safe conversion operators for converting non-const os_members to non-const references to an instance of a subtype.

      operator os_member_variable&() ;
      operator os_field_member_variable&() ;
      operator os_relationship_member_variable&() ;
      operator os_member_function&() ;
      operator os_access_modifier&() ;
      operator os_member_type&() ;
If an attempt is made to perform an inappropriate conversion, err_mop_illegal_cast is signaled.

The Class os_member_variable

Attributes of
os_member_variable
This diagram shows some of the important attributes of os_member_variable.

See os_member_variable in Chapter 2, Class Library, of the ObjectStore C++ API Reference for a complete description of this class.

Create Function

This class has one create function:

      static os_member_variable &create (
            const char *name ,
            os_type *value_type
      ) ;
Function arguments
The arguments specify the initial values for the attributes name and type.

The initial values for the remaining attributes are as follows:
AttributeValue
storage_classos_member_variable::Regular
is_field0
is_static0
is_persistent0

The name Attribute

The name of an os_member_variable is its unqualified name (for example, components rather than part::components).

Getting the attribute
You can get the value of name with

      const char *get_name() const ;
Setting the attribute
You can set the value of name with

      void set_name(const char *name) ;
Initialization
You can initialize name with os_member_variable::create().

The type Attribute

The type of a given os_member_variable is its value type. You can retrieve an os_member_variable's type with

Getting the attribute
      const os_type &get_type() const ;
      os_type &get_type() ;
Setting the attribute
You can set the type with

      void set_type(os_type&) ;
Initialization
You can initialize type with os_member_variable::create().

The storage_class Attribute

The value of storage_class for a given os_member_variable is one of the following enumerators:

      os_member_variable::Regular
      os_member_variable::Persistent
      os_member_variable::Static
Initialization
This attribute is initialized to os_member_variable::Regular by os_member_variable::create().

Getting the attribute
You can get the value of storage_class with

      os_unsigned_int32 get_storage_class() const ;
Setting the attribute
You can set storage_class with

      void set_storage_class(os_unsigned_int32) ;

The is_field Attribute

This attribute is nonzero (true) for all and only instances of os_field_member_variable.

Initialization
It is initialized to nonzero by os_field_member_variable::create(), and is initialized to 0 by os_member_variable::create() and os_relationship_member_variable::create().

Getting the attribute
You can retrieve the value of is_field with

      os_boolean is_field() const ;

The is_static Attribute

The attribute is_static indicates whether a given os_member_variable is static.

Initialization
Initializing or setting the attribute storage_class causes is_static to be set automatically.

Getting the attribute
You can retrieve the value of is_static with

      os_boolean is_static() const ;

The is_persistent Attribute

The attribute is_persistent indicates whether a given os_member_variable is a persistent data member.

Initialization
Initializing or setting the attribute storage_class causes is_persistent to be set automatically.

Getting the attribute
You can retrieve the value of is_persistent with

      os_boolean is_persistent() const ;

Type-Safe Conversion Operators

The class os_member_variable defines type-safe conversion operators for converting const os_member_variables to const references to an instance of a subtype.

      operator const os_field_member_variable&() const ;
      operator const os_relationship_member_variable&() const ;
There are also type-safe conversion operators for converting non-const os_member_variables to non-const references to an instance of a subtype.

      operator os_field_member_variable&() ;
      operator os_relationship_member_variable&() ;
These functions signal err_mop_illegal_cast if an attempt is made to perform an inappropriate conversion.

The Class os_relationship_member_variable

Attributes of os_relationship_member_variable
Here is a diagram showing some of the important members of os_relationship_member_variable. Note that attributes inherited from os_member_variable and os_member are not shown.

See os_relationship_member_variable in Chapter 2, Class Library, of the ObjectStore C++ API Reference for a complete description of this class.

Create Function

This class has one create function:

      static os_relationship_member_variable &create (
            const char *name ,
            const os_type *value_type,
            const os_class_type *related_class,
            const os_relationship_member_variable *related_member
      ) ;
Function arguments
The arguments specify the initial values for the attributes name, type, related_class, and related_member.

The initial values for the remaining attributes are as described for os_member_variable::create().

The related_class Attribute

The related_class of a given os_relationship_member_variable is the class defining the inverse of the given member.

Getting the attribute
You can get the related_class with

      const os_class_type &get_related_class() const ;
      os_class_type &get_related_class() ;
Setting the attribute
You can set the related_class with

      void set_related_class(os_class_type&) ;

The related_member Attribute

The related_member of a given os_relationship_member_variable is the inverse member of the given member.

Getting the attribute
You can get the value of this attribute with

      const os_relationship_member_variable &
                  get_related_member() const ;
      os_relationship_member_variable &get_related_member() ;
Setting the attribute
You can set the related_member with

      void set_related_member(os_relationship_member_variable&) ;

The Class os_field_member_variable

Attribute of os_field_member_variable
Here is a diagram of the attributes of os_field_member_variable. Note that attributes inherited from os_member_variable and os_member are not shown.

See os_field_member_variable in Chapter 2, Class Library, of the ObjectStore C++ API Reference for a complete description of this class.

Create Functions

This class has one create function:

      static os_field_member_variable &create (
            const char *name ,
            const os_type *value_type,
            os_unsigned_int8 size_in_bits
      ) ;
Function arguments
The arguments specify the initial values for the attributes name, type, and size. If value_type is not 0, a pointer to an os_integral_type, or a pointer to an os_enum_type, err_mop is signaled.

The initial values for the remaining attributes are as described for os_member_variable::create().

The size Attribute

The size of a given os_field_member_variable is the number of bits in the value of the given member.

Getting the attribute
You can get the size with

      os_unsigned_int8 get_size() const ;
Setting the attribute
You can set the size with

      void set_size(os_unsigned_int8) ;

The Class os_access_modifier

Attribute of os_access_modifier
An os_access_modifier represents an access modification performed by a class on an inherited member. Note that attributes inherited from os_member are not shown.

See os_access_modifier in Chapter 2, Class Library, of the ObjectStore C++ API Reference for a complete description of this class.

Create Function

This class has one create function:

      static os_access_modifier &create(os_member*) ;
Function argument
The argument is used to initialize the base_member attribute.

The base_member Attribute

The base_member of a given os_access_modifier is the member whose access is being modified.

Getting the attribute
You can get this with

      const os_member &get_base_member() const ;
      os_member &get_base_member() ;
Setting the attribute
You can set this with

      void set_base_member(os_member&) ;

The Class os_enum_type

Attributes of os_enum_type
Here is a diagram showing the attributes of os_enum_type. Note that the attributes inherited from os_type are not shown.

See os_enum_type in Chapter 2, Class Library, of the ObjectStore C++ API Reference for a complete description of this class.

Create Function

This class has one create function:

      static os_enum_type &create (
            const char *name,
            const os_List<os_enumerator_literal*> &enumerators
      ) ;
Function arguments
The arguments initialize the name and enumerators attributes.

The name Attribute

The value of this attribute for an os_enum_type is the os_enum_type's name.

Getting the attribute
You can get this attribute with

      const char *get_name() const ;
Setting the attribute
You can set the attribute with

      void set_name(const char *name) ;

The enumerators Attribute

The value of this attribute for a given os_enum_type is a list of the enumerators of the given type, that is, an os_List<os_enumerator_literal*>.

Getting the attribute
You can get the value with

      os_List<const os_enumerator_literal*> 
                  get_enumerators() const ;
      os_List<os_enumerator_literal*> get_enumerators() ;
Setting the attribute
You can set the value with

      void set_enumerators(const os_List<os_enumerator_literal*>&) ;

The Class os_enumerator_literal

Attributes of os_enumerator_literal
Instances of this class represent enumerators. Here is a diagram showing the attributes of os_enumerator_literal:

See os_enumerator_literal in Chapter 2, Class Library, of the ObjectStore C++ API Reference for a complete description of this class.

Create Function

This class has one create function:

      static os_enumerator_literal &create (
            const char *name,
            os_int32 value
      ) ;
Function arguments
The arguments specify the initial values for name and value.

The name Attribute

The value of name for a given os_enumerator_literal is its unqualified name (for example, ordered rather than os_collection::ordered).

Getting the attribute
You can get the name with

      const char *get_name() const ;
Setting the attribute
You can set the name with

      void set_name(const char *name) ;
Initialization
The name is initialized by the create function.

The Class os_void_type

Instances of this class represent the type void, which can be used as a return type for functions, or can be used in conjunction with os_pointer_type to form the type void*.

See os_void_type in Chapter 2, Class Library, of the ObjectStore C++ API Reference for a complete description of this class.

Create Function

This class's create function is

      static os_void_type &create() ;

The Class os_pointer_type

Attribute of
os_pointer_type
Instances of this class are used to represent pointer types

.

See os_pointer_type in Chapter 2, Class Library, of the ObjectStore C++ API Reference for a complete description of this class.

Create Function

The create function for this class is

      static os_pointer_type &create(os_type *target_type) ;
Function argument
The argument is used to initialize the attribute target_type.

The target_type Attribute

The value of this attribute for a given os_pointer_type is the type of object pointed to by instances of the given pointer type. For example, the target_type of an os_pointer_type representing part* is an os_type representing the type part.

Getting the attribute
You can get the target_type of a given os_pointer_type with

      const os_type &get_target_type() const ;
      os_type &get_target_type() ;
If no type was specified when the os_pointer_type was created (that is, if the class argument to create() was 0), the unspecified type is returned. This is the only os_type for which os_type::is_unspecified() returns nonzero (see The is_unspecified() function).

Setting the attribute
You can set the target_type with

      void set_target_type(os_type&)
This attribute is initialized with the create function.

Type-Safe Conversion Operators

The class os_pointer_type defines type-safe conversion operators for converting const os_pointer_types to const references to an instance of a subtype.

      operator const os_pointer_to_member_type&() const ;
      operator const os_reference_type&() const ;
There are also type-safe conversion operators for converting non-const os_pointer_types to non-const references to an instance of a subtype.

      operator os_pointer_to_member_type&() const ;
      operator os_reference_type&() ;
These functions signal err_mop_illegal_cast if an attempt is made to perform an inappropriate conversion.

The Class os_reference_type

Attribute of
os_reference_type
Instances of this class are used to represent reference types. Note that the following diagram shows attributes inherited from os_pointer_type, but not those inherited from os_type.

See os_reference_type in Chapter 2, Class Library, of the ObjectStore C++ API Reference for a complete description of this class.

Create Function

The create function for this class is

      static os_reference_type &create(os_type *target_type) ;
Function argument
The argument is used to initialize the attribute target_type.

The target_type Attribute

The value of this attribute for a given os_reference_type is the type of object pointed to by instances of the given pointer type. For example, the target_type of an os_reference_type representing part& is an os_type representing the type part.

Getting and setting this attribute
You can get and set the target_type with functions inherited from os_pointer_type.

The Class os_pointer_to_member_type

Attributes of
os_pointer_to_member_type
Instances of this class are used to represent pointer-to-member types. Note that the following diagram shows attributes inherited from os_pointer_type, but not those inherited from os_type.

See os_pointer_to_member_type in Chapter 2, Class Library, of the ObjectStore C++ API Reference for a complete description of this class.

Create Function

The create function for this class is

      static os_pointer_to_member_type &create (
            os_type *target_type,
            os_class_type *target_class
      ) ;
Function argument
The argument is used to initialize target_class and target_type.

The target_type Attribute

The value of this attribute for a given os_pointer_to_member_type is the type of object referred to by instances of the given pointer type.

Getting and setting this attribute
You can get and set the target_type with functions inherited from os_pointer_type.

The target_class Attribute

The value of this attribute for a given os_pointer_to_member_type is the class defining the pointed-to member.

Getting the attribute
You can get the target_class of a given os_pointer_to_member_type with

      const os_class_type &get_target_class() const ;
      os_class_type &get_target_class() ;
If no class was specified when the os_pointer_to_member_type was created (that is, if the target_class argument to create() was 0), the unspecified type is returned. This is the only os_type for which os_type::is_unspecified() returns nonzero (see The is_unspecified() function).

Setting this attribute
You can set the target_class with

      void set_target_class(os_class_type&)
Initialization
This attribute can be initialized with the create function as well.

The Class os_indirect_type

Attribute of
os_indirect_type
Instances of this type are direct instances of either os_indirect_type or os_anonymous_indirect_type.

See os_named_indirect_type (page 247) and os_anonymous_indirect_type (page 249).

See also os_indirect_type in Chapter 2, Class Library, of the ObjectStore C++ API Reference for a complete description of this class.

The Class os_named_indirect_type

Attribute of os_named_indirect_type
An instance of this class represents a C++ typedef. The diagram below shows the attribute target_type, inherited from os_indirect_type, but it does not show attributes inherited from os_type.

See os_named_indirect_type in Chapter 2, Class Library, of the ObjectStore C++ API Reference for a complete description of this class.

Create Function

The create function for this class is

      static os_named_indirect_type &create (
            os_type *target_type,
            const char *name
      ) ;
Function arguments
The arguments are used to initialize target_type and name.

The target_type Attribute

The value of this attribute for a given os_named_indirect_type is the type named by the typedef.

Getting the attribute
You can get the target_type of a given os_named_indirect_type with

      const os_type &get_target_type() const ;
      os_type &get_target_type() ;
Setting the attribute
You can set the target_type with

      void set_target_type(os_type&)
Initialization
This attribute can be initialized with the create function.

The name Attribute

The value of name for a given os_named_indirect_type is the name the typedef introduces.

Getting the attribute
You can get the name with

      const char *get_name() const ;
Setting the attribute
You can set the name with

      void set_name(const char *name) ;
Initialization
name can be initialized by the create function as well.

The Class os_anonymous_indirect_type

Member functions of os_anonymous_indirect_type
An instance of this class represents a const or volatile type. The diagram below shows the attribute target_type, inherited from os_indirect_type, but it does not show attributes inherited from os_type.

See os_anonymous_indirect_type in Chapter 2, Class Library, of the ObjectStore C++ API Reference for a complete description of this class.

Create Function

The create function for this class is

      static os_anonymous_indirect_type &create (
            os_type *target_type
      ) ;
Function argument
The argument is used to initialize target_type.

The target_type Attribute

The value of this attribute for a given os_anonymous_indirect_type is the type to which the const or volatile specifier applies. For example, the type const int is represented as an instance of os_anonymous_indirect_type whose target_type is an instance of os_integral_type.

Getting the attribute
You can get the target_type of a given os_anonymous_indirect_type with

      const os_type &get_target_type() const ;
      os_type &get_target_type() ;
Setting the attribute
You can set the target_type with

      void set_target_type(os_type&)
Initialization
This attribute can be initialized with the create function as well.

The is_const Attribute

The value of is_const is nonzero for const types and 0 otherwise.

Getting the attribute
You can get the value with

      os_boolean is_const() const ;
Setting the attribute
You can set the value with

      void set_is_const(os_boolean) ;

The is_volatile Attribute

The value of is_volatile is nonzero for volatile types and 0 otherwise.

Getting the attribute
You can get the value with

      os_boolean is_volatile() const ;
Setting the attribute
You can set it with

      void set_is_volatile(os_boolean) ;

The Class os_array_type

Attributes of
os_array_type
Instances of this class are used to represent array types. Note that attributes inherited from os_type are not shown

.

See os_array_type in Chapter 2, Class Library, of the ObjectStore C++ API Reference for a complete description of this class.

Create Function

This class has one create function:

      static os_array_type &create(
            os_unsigned_int32 number_of_elements,
            os_type *element_type
      ) ;
Function arguments
The arguments initialize the attributes number_of_elements and element_type.

The number_of_elements Attribute

The value of this attribute for a given os_array_type is the number of elements that instances of the array type are declared to accommodate.

Getting the attribute
You can get this attribute with

      os_unsigned_int32 get_number_of_elements() const ;
Setting the attribute
You can set it with

      void set_number_of_elements(os_unsigned_int32) ;

The element_type Attribute

The value of this attribute for a given os_array_type is the type of element that instances of the array type are declared to have.

Getting the attribute
You can get this type information with

      const os_type &get_element_type() const ;
      os_type &get_element_type() ;
Setting the attribute
You can set it with

      void set_element_type(os_type&) ;

Fetch and Store Functions

ObjectStore provides a number of global (that is, nonmember) functions that allow you to fetch the value of a specified data member (specified with an os_member_variable) for a specified object, and to store a specified value in a specified data member for a specified object. There are different functions for fetching or storing different types of values.

The first parameter to functions in the os_fetch and os_store classes is a void pointer to an arbitrary object. This pointer must refer to the closest containing class. If, for example, the relevant member variable being accessed is part of an inherited class, you must pass the address of the base class, not the outermost class. If you pass the wrong pointer, you will end up accessing the wrong address.

The os_fetch() Functions

Note that the os_fetch() functions store a reference to the fetched value in the argument value, and also return the value.

The first parameter to os_fetch and os_store is a void pointer to an arbitrary object. This pointer must refer to the closest containing class. If, for example, the relevant member variable being accessed is part of an inherited class, you must pass the address of the base class, not the outermost class. If you pass the wrong pointer, you will end up accessing the wrong address.

For more information, see ::os_fetch() in Chapter 3, System-Supplied Global Functions, of the ObjectStore C++ API Reference.

void* os_fetch(
      const void *p, const os_member_variable&, void *&value);
unsigned long os_fetch(
      const void *p, const os_member_variable&, 
unsigned long &value);
long os_fetch( const void *p, const os_member_variable&, long &value); unsigned int os_fetch( const void *p, const os_member_variable&,
unsigned int &value);
int os_fetch( const void *p, const os_member_variable&, int &value); unsigned short os_fetch( const void *p, const os_member_variable&,
unsigned short &value);
short os_fetch( const void *p, const os_member_variable&, short &value); unsigned char os_fetch( const void *p, const os_member_variable&,
unsigned char &value);
char os_fetch( const void *p, const os_member_variable&, char &value); float os_fetch( const void *p, const os_member_variable&, float &value); double os_fetch( const void *p, const os_member_variable&, double &value); long double os_fetch( const void *p, const os_member_variable&, long double &value);

The os_store() Functions

For more information, see ::os_store() in Chapter 3, System-Supplied Global Functions, of the ObjectStore C++ API Reference.

void os_store(
      void *p, const os_member_variable&, const void *value);
void os_store(
      void *p, const os_member_variable&,
const unsigned long value);
void os_store( void *p, const os_member_variable&, const long value); void os_store( void *p, const os_member_variable&, const unsigned int value); void os_store( void *p, const os_member_variable&, const int value); void os_store( void *p, const os_member_variable&, const unsigned short value); void os_store( void *p, const os_member_variable&, const short value); void os_store( void *p, const os_member_variable&, const unsigned char value); void os_store( void *p, const os_member_variable&, const char value); void os_store( void *p, const os_member_variable&, const float value); void os_store( void *p, const os_member_variable&, const double value); void os_store( void *p, const os_member_variable&, const long double value);

Type Instantiation

You can instantiate the class represented by a given os_class_type by calling the global function ::operator new() without a constructor call. Use the function os_type::get_size() to supply the size_t argument to ::operator new(). A void* is returned. For example:

      void *a_part_ptr = ::operator new( 
            the_class_part.get_size(), 
            db, 
            &part_typespec 
      ) ;
You can use the function ::os_store() to initialize the new instance.

Example: Schema Read Access

This section presents an example that illustrates the use of the metaobject protocol. The example consists of a routine that prints information about a specified class instance: its class, its address, and the values of its data members. The routine is generic instead of being class specific, so it can operate on an instance of any class. This is what necessitates the use of the metaobject protocol - schema information must be accessed in order to identify, at run time, the members of the specified object, so their values can be fetched and presented. Something like this functionality might be offered by a browser or debugger.

The Top-Level print() Function

This example involves several functions. The function print() below is the top-level function. As arguments it takes a pointer to an object and an os_class_type& representing the object's type. There are two other arguments, member_prefix and indentation, that are supplied only when the function calls itself recursively (see below).

print() function definition
      #include <ostore/mop.hh>
      const os_unsigned_int32 max_buff_size = 100 ;
      static void print(const void* p, const os_class_type& c,
            char* member_prefix = "", os_unsigned_int8 indentation = 0 
      {
            if (!*member_prefix)
                  fprintf(stdout, "\n%sclass %s /* 0x%x */ {\n", 
                        indent(indentation), c.get_name(), p) ;
            os_Cursor<const os_base_class*> bc(c.get_base_classes()) ;
            for (const os_base_class* b=bc.first(); b ; b = bc.next()) {
                  char buff[1024] ;
                  os_strcpy(buff, member_prefix) ;
                  os_strcat(buff, b->get_class().get_name()) ;
                  os_strcat(buff, "::") ;
                  print((void*) ((char*)p+b->get_offset()), b->get_class(),
                        buff, indentation) ;
            }  /* end of for loop */
            os_Cursor<const os_member*> mc(c.get_members()) ;
            for (const os_member* m=mc.first(); m ; m=mc.next())
                        switch (m->kind()) 
            {
                  case os_member::Variable:
                  case os_member::Relationship:
                  case os_member::Field_variable: 
                  {
                        const os_member_variable& mv = *m ;
                        char* type_string = mv.get_type().get_string() ;
                        if (mv.is_static() || mv.is_persistent()) 
                              continue ;
                        fprintf(stdout, "%s %s\t%s%s = ",
                              indent(indentation), type_string,
                              member_prefix, mv.get_name()) ;
                        print(p, mv, indentation) ;
                        fprintf(stdout, " ;\n") ;
                        delete [] type_string ;
                        break ;
                  } /* end of case statement*/
            }  /* end of for loop */
            if (!*member_prefix)
                  fprintf(stdout, "%s}", indent(indentation)) ;
      } /* end of print() function */
Let us explain the function by considering some sample input.

Sample input: class definitions
Suppose an application uses the classes part, mechanical_part, and date, with the data members shown in the following definitions:

      class date {
            private:
                  int day;
                  int month;
                  int year;
            public:
                  date(int dd, int mm, int yy) {
                        day = dd; month = mm; year = yy;
                  }
                  . . . 
      };
      class part {
            private:
                  int part_id;
                  date date_created;
            public:
                  part(int id, date d) {part_id = id; date_created = d;}
            . . . 
      };
      class mechanical_part : public part {
            private:
                  mechanical_part *parent;
            public:
                  mechanical_part(int id, date d, mechanical_part *p) : 
                        part(id, d) {parent = p;}
            . . . 
      };
Creating objects
And suppose you create objects like this:

      date d(1, 15, 1993);
      mechanical_part *parent = new(db) mechanical_part(1, d, 0);
      mechanical_part *child = new(db) mechanical_part(2, d, parent);
Pass child to print()
Finally, suppose you pass child to print():

      print(child, os_type::type_at(child));
print() begins with a check of the argument member_prefix, which defaults to a pointer to the null character (0). Since this is 0, you have just started printing an object, and so the function outputs the name of the object's class with a call to os_class_type::get_name():

      c.get_name()
The object's address is also printed.

Sample output
For the sample input, the output so far might look like this:

      class mechanical_part /* 0xCB320 */ {

Recursive Execution of print()

Iterating through the base types
Next, the function iterates through the collection of the specified type's base types, obtained with a call to os_class_type::get_base_classes():

      c.get_base_classes()
For each base class, the function prints the portion of the specified object that corresponds to that base class. It does this by calling itself recursively, specifying the address of the appropriate subobject, and specifying the base type as its type.

How the object's address is obtained

The address of the appropriate object is obtained by adding the base type's offset to p, the address of the original object. The offset is obtained using os_base_class::get_offset().

How the object's type is obtained

The type is obtained using os_base_class::get_class(). Remember, an os_base_class encapsulates information about the derivation of one class from another (for example, the offset of the base class within instances of the derived class - which you just used). An os_base_class is not itself an os_class_type. To get the associated os_class_type object, you use get_class().

For the sample input, the only base class is part, so an os_class_type& representing part is passed as the second argument in the recursive call to print().

In addition, since this is a recursive call, a member prefix and indentation are passed as well. The prefix consists of the base type's name followed by :: (part:: for the sample input). This will be used when printing the names of data members defined by the base type. By using a qualified name for a base class member, the output identifies the defining class.

During the execution of the recursive call, first the member prefix is tested. Since it is nonnull, you do not print the header, class class-na /* address */ {

Iterating through the base classes of the specified class
Next you iterate through the base classes of the specified class, part in this case. This takes care of subobjects corresponding to indirect base classes. Since part itself has no base classes, this loop is null for the sample input.

Iterating through members of the specified class
Then you iterate through the collection of members of the specified class, obtained with a call to os_class_type::get_members():

      c.get_members()
Since you are within the recursive execution, the specified class is part. You test the kind of each member using os_member::kind(), and for each nonstatic, nonpersistent data member, you output the data member's name (using member_prefix) and type, and call an overloading of print() that prints data member values (described below).

Converting
os_member to
os_member_variable
This involves first converting the os_member to an os_member_variable:

      const os_member_variable &mv = *m;
Recall that there are type-safe conversions from os_member to const os_member_variable&, as well as to all the other subtypes of os_member.

You get the value type of the member using os_member_variable::get_type() and you get the name of this type using os_type::get_string():

      char *type_string = mv.get_type().get_string()
Note that get_string() allocates a character array, which is deleted when no longer needed:

      delete [] type_string;
Sample output: first member of part
For the sample input, the output after retrieving the first member of part might look like this:

            class mechanical_part /* 0xCB320 */ {
                  int part::part_id =
print() function for data members
Now consider the function print() for data members, which is called in the line

      print(p, mv, indentation);
Here is how this function is defined:

      /* Prints the value at p. It is the value of the data member */
      /* indicated by the "m" argument */
      static void print(const void* p,
             const os_member_variable& m,
             const os_unsigned_int8 indentation)
      {
            const os_type& mt = m.get_type().strip_indirect_types() ;
            if (mt.is_integral_type()) {
                  if (((const os_integral_type&)mt).is_signed()) {
                        os_int32 value ;
                        fprintf(stdout, "%ld", os_fetch(p, m, value)) ;
                  } /* end if */
                  else {
                        os_unsigned_int32 value ;
                        fprintf(stdout, "%lu", os_fetch(p, m, value)) ;
                  } /* end else */
                  return ;
            } /* end if */
            else if (mt.kind()==os_type::Enum) {
                  os_int32 value = 0 ;
                  const os_enumerator_literal* lit=
                        ((os_enum_type&)mt).get_enumerator(
os_fetch(p, m, value)) ;
fprintf(stdout, "%ld(%s)", value, (lit ? lit->get_name() : "?enum literal?" )) ; return ; } /* end else if */ switch (mt.kind()) { case os_type::Float: { float value ; fprintf(stdout, "%f", os_fetch(p, m, value)) ; return ; } case os_type::Double: { double value ; fprintf(stdout, "%lg", os_fetch(p, m, value)) ; return ; } case os_type::Long_double: { long double value ; fprintf(stdout, "%lg", os_fetch(p, m, value)) ; return ; } case os_type::Pointer: case os_type::Reference: print_a_pointer((char*)p+m.get_offset()) ; return ; case os_type::Class: case os_type::Instantiated_class: print((char*)p+m.get_offset(), (const os_class_type&)mt, "", indentation+1) ; return ; case os_type::Array: print((char*)p+m.get_offset(), (const os_array_type&)mt, indentation+1) ; return ; default: /* print its address */ fprintf(stdout, "*0x%x*", (char*)p + m.get_offset()) ; } /* end switch */ }
Behavior of print() for data members
This function begins by retrieving the value type of the specified data member, and applying strip_indirect_types(). The result will be an os_type that is not an os_indirect_type. Next, it determines the kind of this type, and acts accordingly.

For the sample input, the member is currently part::part_id and the value type is int. This is a signed integer type, so the value is printed with the format "%ld". The value is obtained with os_fetch():

      os_fetch(p, m, value);
Sample output: data members
When this function returns, the output would be

            class mechanical_part  /* 0xCB320 */ {
                  int part::part_id = 2
For the next member, part::date_created, the output is supplemented to look like

            class mechanical_part  /* 0xCB320 */ {
                  int part::part_id = 2
                  date date_created =
before print() for member values is called again.

Next recursion of print()
Now the value type of part::date_created is determined to be a class, so the original print() function is called recursively again. This will put us two levels of recursion down from the top-level execution of print().

      print((char*)p+m.get_offset(), (const os_class_type&)mt, "", 
                  indentation+1) ;
The arguments are a pointer to the data member value (obtained with the help of os_member_variable::get_offset()) and the member's value type (which you cast to a const os_class_type&).

This call to print() supplements the output with a representation of the date object that serves as the data member value:

            class mechanical_part /* 0xCB320 */ {
                  int part::part_id = 2
                  date part::date_created = class date /* 0xCB324 */ {
                        int day = 1
                        int month = 1
                        int year = 1993
                  }
Exit from base class loop of print()
Now you have finished the portion of the object corresponding to the base class part, and you pop up to the top-level print() execution and exit from the base class loop. Then you handle the members defined by the object's direct type, mechanical_part.

Looping through the class's members
This involves looping through that class's members, and presenting the data members. This class defines one data member, mechanical_part::parent, whose value type is part*. So print() supplements the output with the member's name and type name:

            class mechanical_part /* 0xCB320 */ {
                  int part::part_id = 2
                  date part::date_created = class date /* 0xCB324 */ {
                        int day = 1
                        int month = 1
                        int year = 1993
                  }
                  part* parent =
and then calls print() for data members.

The print_a_pointer() function

This function determines that the value type of the current member is a pointer type, and so it calls print_a_pointer() on the data member's address.

print_a_pointer() definition
/* print a pointer value along with as much useful info as possible */
static void print_a_pointer(const void** p) 
{
      static os_type::os_type_kind string_char =
                  char(0x80) > 0 ? os_type::Signed_char 
                  os_type::Unsigned_char ;
      if (*p) 
      {
            const os_type* type = os_type::type_at(*(void**)p) ;
            char* tstr = type ? type->get_string() : os_strdup("???") ;
            fprintf(stdout, "(%s*)%#lx%s",
                        tstr, (unsigned long)*(void**)p,
                        ((type && (type->kind() == string_char)) ?
                        get_string((char*)*p, string_char) : "")) ;
            delete tstr ;
            const void* op = 0 ;
            os_unsigned_int32 ecount = 0 ;
            const os_type* otype = os_type::type_containing(
                  *p, op, ecount) ;
            if (op && (op != *p) && (otype != type)) 
            {
                  /* the enclosing object is different */
                  if (ecount > 1) 
                  {
                        /* point to the appropriate array element */
                        os_unsigned_int32 offset = (char*)op - (char*)*p ;
                        os_unsigned_int32 i = offset / otype->get_size() ;
                        op = (char*)op + (i * otype->get_size()) ;
                  } /* end if */
                  char* tstr = type ? otype->get_string() : os_strdup("???") ;
                  fprintf(stdout, " /* enclosing object @ (%s*)%#lx */ ",
                              tstr, (unsigned long)op) ;
                  delete tstr ;                   
            } /* end if */
      } /* end if */
      else fprintf(stdout, "0") ;
} /* end print_a_pointer() */
print_a_pointer() prints the specified pointer's type and value, and, if the pointer is a char*, it prints up to the first 100 characters of the designated string (with the help of get_string(), shown below). In addition, if the object pointed to is embedded in some other object or array, the type and address of the enclosing object are printed.

Sample output from print_a_pointer()
So the sample output might end up like this:

      class mechanical_part /* 0xCB322 */ {
            int part::part_id = 2
            date part::date_created = class date /* 0xCB326 */ {
                  int day = 1
                  int month = 1
                  int year = 1993
            }
            part* parent = (part*) 0xCB300
      }

Other Data Handling Routines

Array-valued data members are handled with the following routines.

The get_string() function
This function builds a printable representation for a char* string and returns it. Only strings up to max_buff_size are printed. If they are longer, they are truncated and a trailing ...%d... is used to indicate the true length.

static char* get_string(const char* p, os_type::
            os_type_kind string_char) 
{
      const void* op = 0 ; os_unsigned_int32 ecount = 0 ;
      /* Ignore embedded strings for now */
      const os_type* otype = os_type::type_containing(p, op, ecount) ;
      /* +5 for the quotes + null character*/
      static char buff[max_buff_size+5]; 
      char *bp = buff ;
      os_strcpy(bp, " \"") ;
      bp += 2 ;
      if (op && otype && (otype->kind() == string_char)) {
            ecount=ecount-(p-(char*)op); 
            /*in case it is pointing into the middle */
            os_unsigned_int32 count = 
                  (ecount <= max_buff_size)? ecount : max_buff_size ;
            for (; count && (*bp = *p); bp++, p++, count--) ;
            if (*p) {
                  /* determine its true length */
                  count = ecount - max_buff_size ;
                  for (; count && (*p); p++, count--) ;
                  ecount -= count ;
                  os_sprintf(bp-(3+10+3), "...%d...", ecount) ;
                  bp = buff + os_strlen(buff) ;
            } /* end if */ 
            os_strcpy(bp, "\" ") ;
      } /* end if */
      else buff[0] = 0 ;
      return buff ;
} /* end of get_string() */
The print() function for an array
Print the value at p as an array. The array is described by the argument at.

      static void print(const void* p, const os_array_type& at, 
                  const os_unsigned_int8 indentation)
      {
            const os_type& element_type = 
                              at.get_element_type().strip_indirect_types();
            fprintf(stdout, " { ") ;
            for (int i = 0; i < at.number_of_elements();
                        i++, p = (char*)p + element_type.get_size())       {
                        print(p, element_type, indentation) ;
                        fprintf(stdout, 
                        "%s", (i+1) == at.number_of_elements() ? 
                        " }" : ", ") ;
            } /* end of for loop */
      }
The print() function for a pointer
Print the value indicated by the pointer p, interpreting it as the type supplied by the argument et.

static void print(const void* p, const os_type& et, 
            const os_unsigned_int8 indentation) {
      switch (et.kind()) {
            case os_type::Unsigned_char:
                  fprintf(stdout, "%lu", (os_unsigned_int32)*
(unsigned char*)p) ;
break ; case os_type::Signed_char: fprintf(stdout, "%ld", (os_int32)*(char*)p) ; break ; case os_type::Unsigned_short: fprintf(stdout, "%lu", (os_unsigned_int32)*
(unsigned short*)p) ;
break ; case os_type::Signed_short: fprintf(stdout, "%ld", (os_int32)*(short*)p) ; break ; case os_type::Integer: fprintf(stdout, "%ld", (os_int32)*(int*)p) ; break ; case os_type::Enum: case os_type::Unsigned_integer: fprintf(stdout, "%lu", (os_unsigned_int32)*
(unsigned int*)p) ;
break ; case os_type::Signed_long: fprintf(stdout, "%ld", (os_int32)*(int*)p) ; break ; case os_type::Unsigned_long: fprintf(stdout, "%lu", (os_unsigned_int32)*
(unsigned int*)p) ;
break ; case os_type::Float: fprintf(stdout, "%f", *(float*)p) ; break ; case os_type::Double: fprintf(stdout, "%lg", *(double *)p) ; break ; case os_type::Long_double: fprintf(stdout, "%lg", *(long double *)p) ; break ; case os_type::Pointer: case os_type::Reference: print_a_pointer((void**)p) ; break ; case os_type::Array: print(p, (const os_array_type &)et, indentation) ; break ; case os_type::Class: case os_type::Instantiated_class: print(p, (const os_class_type&)et, "", indentation+1) ; return ; default: /* a type we do not understand how to print */ fprintf(stdout, "?%s?", et.kind_string(et.kind())) ; break ; } /* end of switch */ }
indent() function for formatting
This function returns a string of blanks corresponding to the indentation specified by the argument ilevel.

      static const char* indent(os_unsigned_int32 ilevel) 
      {
            static char indent_string[256] ;
            static os_unsigned_int32 maxilevel = 0, cilevel = 0 ;
            const os_unsigned_int32 indent_tab = 3 ;
            if (ilevel > (256/indent_tab)) ilevel = 256/indent_tab ;
            if (ilevel <= maxilevel) {
                  indent_string[cilevel*indent_tab]= ` ` ;
                  indent_string[ilevel*indent_tab]= 0 ;
                  cilevel = ilevel ;
                  return indent_string ;
            } /* end if */
            os_unsigned_int32 limit = ilevel * indent_tab ;
            for (os_unsigned_int32 i = maxilevel*indent_tab; i < limit; i++)
                  indent_string[i] = ` ` ;
            maxilevel = cilevel = ilevel ;
            return indent_string ;
      }

Example: Dynamic Type Creation

Here is an example of using the metaobject protocol to create types and update schemas.

Overview of the gen_schema() Example

The example centers around a function, gen_schema(), that might serve as the back end of a much simplified schema designer application. The front end would be a tool for drawing an entity-relationship diagram. An entity-relationship diagram is a graph in which the nodes represent types and the arcs represent possible relationships between instances of the types. The schema designer would translate such a diagram into a set of C++ classes, with one or a pair of data members corresponding to each arc in the diagram.

Entity-relationship diagram for the example


The arcs (represented as arrows in the diagram) have single or double arrows at one or both ends. Here is what the arrows mean:

Schema class definitions for the example
The entity-relationship diagram represents the following schema:

      class part {
            public:
                  int part_id ;
                  os_collection &components ;
                  os_collection &resp_engs ;
            } ;
      class employee {
            public:
                  department *head_of ;
                  department *dept ;
                  os_collection &part_resp_for ;
            } ;
      class department
      {
            public:
                  employee *dept_head ;
                  os_collection &emps ;
      } ;
Note that if a single arrow points to a node with a class name as label, a pointer to that class is used as the value type of the corresponding data member. This is a simple way to prevent circular dependencies (assuming arrays of classes are not used). Note also that double arrows correspond to data members whose value type is os_collection&. A future release will support the dynamic creation of parameterized types, so it will be possible to use, for example, os_Collection<part*>&, instead of os_collection&.

The gen_schema() Function

Function arguments
The function gen_schema() takes as argument an entity-relationship diagram represented as an os_Collection<arc*>. An arc has two associated nodes and two associated labels. It also has two associated ends, each of which can have no arrow, a single arrow, or a double arrow.

node and arc class definitions
Here are the definitions of the classes node and arc as defined in the graph.hh header file:

      /* graph.hh */
      #include <string.h>
      enum end_enum { no_arrow, single_arrow, double_arrow } ;
      class node {
            public: 
                  char *label ;
                  static os_typespec *get_os_typespec() ;
                  node ( char *l ) ;
      };
      class arc {
            public:
                  node *node_1 ;
                  node *node_2 ;
                  end_enum end_1 ;
                  end_enum end_2 ;
                  char *label_1 ;
                  char *label_2 ;
            static os_typespec *get_os_typespec() ;
            arc (
                  node *n1,
                  node *n2,
                  end_enum e1,
                  end_enum e2,
                  char *l1,
                  char *l2
            ) ;
      };
node and arc constructors
Here are the implementations of the node and arc constructors, as defined in the graph.cc program file:

      /* graph.cc */
      #include <ostore/ostore.hh>
      #include "graph.hh"
      arc::arc (
            node *n1,
            node *n2,
            end_enum e1,
            end_enum e2,
            char *l1,
            char *l2
      ) {
            node_1 = n1;
            node_2 = n2;
            end_1 = e1;
            end_2 = e2;
            if (l1) {
                  label_1 = new(
                        os_segment::of(this), 
                        os_typespec::get_char(), 
                        strlen(l1) + 1
                  ) char[strlen(l1) + 1];
            strcpy(label_1, l1);
            } /* end if */
            else
                  label_1 = 0;
            if (l2) {
                  label_2 = new(
                        os_segment::of(this), 
                        os_typespec::get_char(),
                        strlen(l2) + 1
                  ) char[strlen(l2) + 1];
            strcpy(label_2, l2);
            } /* end if */
            else 
                  label_2 = 0;
      }
      node::node ( char *l ) {
            label = new(
                  os_segment::of(this), 
                  os_typespec::get_char(),
                  strlen(l) + 1
            ) char[strlen(l) + 1];
            strcpy(label, l);
      }

Supporting Functions for the gen_schema() Application

The function gen_schema() is supported by five other functions that we have defined:

The function gen_schema() processes each arc in the diagram one at a time. For each arc it first looks at the two associated nodes. gen_schema() performs ensure_in_trans() on each of the two nodes.

ensure_in_trans() determines whether there is a type in the transient schema whose name is the node's label. If there is not, it determines whether there is a type in the application schema whose name is the node's label. If there is, ensure_in_trans() copies it to the transient schema (using copy_to_trans()). If there is not, ensure_in_trans() creates a class with that name. It returns a pointer to the newly created, copied, or retrieved type.

Copying types from the application schema to the transient schema is the typical means of getting ObjectStore system-supplied classes into the transient schema. Note, however, that built-in C++ types, like int, are already present in the transient schema.

Notice also that lookups in the application schema and copying from the application schema must be performed within a transaction, since they are operations on a database (the application schema database).

Next gen_schema() determines which of the following eight cases applies to the arc at hand:

In each case one or two data members are created, depending on whether there are arrows at one or both ends. A future release will support the dynamic creation of ObjectStore relationship members, so it will be possible to create relationship members in the case where an arc has arrows at both ends. For now, the example just creates regular data members.

Each data member is created as well as added to the appropriate defining class. This is accomplished by add_single_valued_data_member() or add_many_valued_member(). Each of these functions creates a data member and then calls add_member(). add_member() adds a specified member to a specified class.

Call Graph of Non-ObjectStore Functions for gen_schema()



Once gen_schema() finishes processing all the arcs, the transient schema contains the schema represented by the diagram.

The gen_schema.cc Source File

Here is the code for gen_schema() and its supporting functions, all of which is contained in the gen_schema.cc file.

      /* gen_schema.cc */
      #include <ostore/ostore.hh>
      #include <ostore/coll.hh>
      #include <ostore/mop.hh>
      #include <stdlib.h>
      #include <iostream.h>
      #include <assert.h>
      #include "graph.hh"
      void error(char *m) {
            cout << m << "\n" ;
            exit (1) ;
      }
add_member() function definition
This function makes new_member a member of defining_class.

      void add_member(os_class_type &defining_class,
            os_member &new_member) {
                  os_List<os_member*> members(
                        defining_class.get_members() 
                  );
            members |= &new_member ;
            defining_class.set_members(members) ;
      }
Notice that os_class_type::get_members() returns an os_List<os_member*>. To add or remove a member, copy the returned list and update the copy. Then pass the list to os_class_type::set_members().

copy_to_trans() function definition
This function copies the class named class_name from the application schema to the transient schema. It returns a pointer to the new copy. If the class cannot be found, the function returns 0.

      os_type *copy_to_trans(const char *class_name) {
            OS_BEGIN_TXN(tx1, 0, os_transaction::update)
                  const os_type *the_const_type_ptr = 
                  os_app_schema::get().find_type(class_name) ;
                  if (!the_const_type_ptr) return 0 ;
                  const os_class_type &the_const_class = 
                        *the_const_type_ptr ;
                  os_Set<const os_class_type*>
                        to_be_copied_to_transient_schema ;
                  to_be_copied_to_transient_schema |= &the_const_class ;
                  os_mop::copy_classes (
                        os_app_schema::get(), 
                        to_be_copied_to_transient_schema
                  ) ;
            OS_END_TXN(tx1) 
            return os_mop::find_type(class_name) ;
      }
Notice that os_mop::copy_classes() requires an os_Set<const os_class_type*>. In order to create this set with the appropriate contents, this function first retrieves a const os_type*, dereferences it, and converts it to a const os_class_type&. The const os_class_type& is then dereferenced and inserted into an os_Set<const os_class_type*>. Next, this set is passed to os_mop::copy_classes(), which copies the set's element into the transient schema.

Finally, os_mop::find_type() is used to retrieve from the transient schema a (non-const) os_type*, which is returned. The function does not simply return the_const_type_ptr, because copy_to_trans() should return a modifiable object, one to which you can add members. Looking up a class in any schema except the transient schema results in a const os_type*. Only a lookup in the transient schema results in a non-const os_type*.

ensure_in_trans() function definition
If no type named type_name is in the transient schema, copy it into the transient schema from the application schema. If no type named type_name is in the application schema, create it in the transient schema. The function returns a reference to type in the transient schema named type_name.

      os_type &ensure_in_trans(const char *type_name){
            os_type *t = os_mop::find_type(type_name) ;
            if (!t)
                  t = copy_to_trans(type_name) ;
            if (!t) {
                  os_class_type &c = os_class_type::create(type_name) ;
                  c.set_is_forward_definition(0) ;
                  c.set_is_persistent(1) ;
                  t = &c ;
            } /* end if */
            return *t ;
      }
When you create a class, the attribute is_forward_definition defaults to true. Here it is set to false after creation, because gen_schema() will generate a class definition for each node that represents a class. Similarly, is_persistent defaults to false. Here, it is set to true so the new class can be installed in a database schema.

add_single_valued_member() function definition
This function creates an os_member_variable with value type value_type, and makes it a member of defining_type. If member_name is null, the function prints an error and exits. If defining_class is not a class, the exception err_mop_illegal_cast is signaled.

      void add_single_valued_member(
                  os_class_type &defining_class,
                  os_type &value_type, 
                  const char *member_name
      ) {
            if (!member_name)
                  error("unspecified member name") ;
            os_member_variable &new_member = 
            os_member_variable::create( member_name, &value_type ) ;
            add_member(defining_class, new_member) ;
      }
Note that while the formal parameter defining_class is of type os_class_type&, the corresponding actual parameter can be typed as os_type&. If you pass in such an actual parameter, MOP invokes os_type::operator os_class_type&(), which converts the actual parameter to an os_class_type&. If the object designated by the actual parameter is not really an instance of os_class_type, the operator signals err_mop_illegal_cast.

add_many_valued_member() function definition
This function creates an os_member_variable with value type os_collection&, and makes it a member of defining_type. If member_name is null, the function prints an error and exits. If defining_class is not a class, err_mop_illegal_cast is signaled.

      void add_many_valued_member(
            os_class_type &defining_class, 
            const char *member_name
      ) {
            if (!member_name)
                  error("unspecified member name") ;
            os_type *the_type_os_collection_ptr = 
            os_mop::find_type("os_collection") ;
            if (!the_type_os_collection_ptr)
                  the_type_os_collection_ptr = 
                  copy_to_trans("os_collection") ;
            if (!the_type_os_collection_ptr)
                  error("Could not find the class os_collection in the \
                  application schema") ;
            os_member_variable &new_member = 
            os_member_variable::create(
                  member_name,
                  &os_reference_type::create(the_type_os_collection_ptr)
            ) ;
            add_member(defining_class, new_member) ;
      }
This function copies the class os_collection from the application schema to the transient schema, if it is not already present in the transient schema.

gen_schema() function definition
This function creates classes in the transient schema database based on the graph specified by the arcs.

      void gen_schema(const os_Collection<arc*> &arcs) {
            /*  process each arc in the graph */
            os_Cursor<arc*> c(arcs) ;
            for (arc *a = c.first(); a; a = c.next()) {
                  os_type &t1 = ensure_in_trans(a->node_1->label) ;
                  os_type &t2 = ensure_in_trans(a->node_2->label) ;
                  /*  handle 1 of 8 cases, depending on arc's arrows */
                  if ( a->end_1 == no_arrow && a->end_2 == single_arrow ) 
                        if (t2.get_kind() != os_type::Class) 
                              add_single_valued_member(
                              t1, /* defining type */
                              t2,/* value type */
                              a->label_2 /* member with value type t2 */
                        ) ;
                        else
                              add_single_valued_member(
                              t1, /*  defining type */
                              os_pointer_type::create(&t2), /*  value type */
                              a->label_2 /* of member with value type t2 */
                        ) ;
                  else if ( a->end_1 == single_arrow && a->end_2 ==
                              no_arrow ) 
                        if (t1.get_kind() != os_type::Class)
                              add_single_valued_member(
                               t2, /*  defining type */
                              t1, /*  value type */
                              a->label_1 /* member with value type t1 */
                        ) ;
                        else
                              add_single_valued_member(
                              t2, /*  defining type */
                              os_pointer_type::create(&t1), /*  value type */
                              a->label_1 /*member with value type t1 */
                        ) ;
                  else if ( a->end_1 == no_arrow && a->end_2 == 
                              double_arrow )
                        add_many_valued_member(
                              t1, /*  defining type */
                              a->label_2 /*  name of many-valued member */
                        ) ;
                  else if ( a->end_1 == double_arrow && a->end_2 == 
                              no_arrow ) 
                        add_many_valued_member(
                              t2, /*  defining type */
                              a->label_1 /*  name of many-valued member */
                        ) ;
                  else if ( a->end_1 ==single_arrow && a->end_2 ==
                              single_arrow ) {
                        /*  binary relationship */
                        add_single_valued_member(
                              t1, /*  defining type */
                              os_pointer_type::create(&t2), /*  value type */
                              a->label_2 /* member with value type t2 */
                        ) ;
                        add_single_valued_member(
                               t2, /*  defining type */
                               os_pointer_type::create(&t1), /*  value type */
                               a->label_1 /* member with value type t1 */
                        ) ; 
                  } /* end of else if */
                  else if ( a->end_1 == single_arrow && a->end_2 == 
                              double_arrow ) {
                        /*  binary relationship */
                        add_single_valued_member(
                              t2, /*  defining type */
                              os_pointer_type::create(&t1), /*  value type */
                               a->label_1 /* member with value type t1 */
                        ) ;
                        add_many_valued_member(
                              t1, /*  defining type */
                              a->label_2 /*  name of many-valued member */
                        ) ;
                  } /* end of else if */
                  else if ( a->end_1 == double_arrow && a->end_2 == 
                              single_arrow ) {
                        /*  binary relationship */
                        add_single_valued_member(
                              t1, /*  defining type */
                              os_pointer_type::create(&t2), /*  value type */
                              a->label_2 /* member with value type t2 */
                        ) ;
                        add_many_valued_member(
                              t2, /*  defining type */
                              a->label_1 /*  name of many-valued member */
                        ) ;
                  } /* end of else if */
                  else if ( a->end_1 == double_arrow && a->end_2 == 
                              double_arrow ) {
                        /*  binary relationship */
                        add_many_valued_member(
                              t1, /*  defining type */
                              a->label_2 /*  name of many-valued member */
                        ) ;
                        add_many_valued_member(
                              t2, /*  defining type */
                              a->label_1 /*  name of many-valued member */
                        ) ;
                  } /* end of else if */
            } /*  finish processing arcs (for loop)*/
      }

The Driver Definition

Here is a driver that creates a graph representing the diagram shown on page 274. It then passes the graph to gen_schema(), which updates the transient schema. Next, the driver installs in the schema of a specified database those classes that are in the transient schema. Finally, it creates an instance of each dynamically created class.

The driver relies on two functions, find_class() and find_member(), to instantiate the dynamically created classes. These supporting functions are shown first.

find_class() function definition
This function returns a reference to the class in the_schema with name class_name. If the class is not found, the function returns an error. If class_name is not a class, err_mop_illegal_cast is signaled.

      const os_class_type &find_class(
            const char *class_name, 
            const os_schema &the_schema
      ) {
            const os_type *the_type_ptr = 
                  the_schema.find_type(class_name) ;
            if (!the_type_ptr)
                  error("Cannot find class with specified name") ;
            return *the_type_ptr ;
      }
find_member_variable() function definition
This function returns a reference to the member of defining_class with name member_name. If member_name is not found, the function returns an error. If member_name is not a data member, err_mop_illegal_cast is signaled.

      const os_member_variable &find_member_variable(
            const os_class_type &defining_class,
            const char *member_name
      ) {
            const os_member *the_member = 
                  defining_class.find_member(member_name) ;
            if (!the_member)
                  error("Could not find member with specified name.") ;
            return *the_member ;
      }
Driver main() function definition
      void main(int, char **argv) {
            objectstore::initialize() ;
            os_collection::initialize() ;
            os_mop::initialize() ;
            if (!argv[1])
                  error("null database name\n") ;
            /*  create a graph representing an entity-relationship diagram */
            /*  the graph is a collection of arcs */
            os_Collection<arc*> &arcs = 
            os_Collection<arc*>::create( 
                  os_database::get_transient_database() 
            ) ;
            node *part_node = new node("part") ;
            node *employee_node = new node("employee") ;
            node *int_node = new node("int") ;
            node *department_node = new node("department") ;
            arcs |= new arc(
                  part_node, 
                  int_node, 
                  no_arrow, 
                  single_arrow, 
                  0, 
                  "part_id"
            ) ;
            arcs |= new arc(
                  part_node, 
                  part_node, 
                  no_arrow, 
                  double_arrow, 
                  0, 
                  "components"
            ) ;
            arcs |= new arc(
                  employee_node, 
                  department_node, 
                  single_arrow, 
                  single_arrow, 
                  "dept_head", 
                  "head_of"
            ) ;
            arcs |= new arc(
                  employee_node, 
                  department_node, 
                  double_arrow, 
                  single_arrow, 
                  "emps", 
                  "dept"
            ) ;
            arcs |= new arc(
                  part_node, 
                  employee_node, 
                  double_arrow, 
                  double_arrow, 
                  "parts_resp_for", 
                  "resp_engs"
            ) ;
            cout << "Calling gen_schema() ...\n" ;
            gen_schema(arcs) ;
            cout << "Schema generated. Installing schema ...\n" ;
            os_database *db = os_database::open(argv[1], 0, 0664) ;
            /*  install schema in db */
            OS_BEGIN_TXN(tx1, 0, os_transaction::update)
                  os_database_schema::get_for_update(*db).install( 
                        os_mop::get_transient_schema() 
                  ) ;
            OS_END_TXN(tx1)
            OS_BEGIN_TXN(tx2, 0, os_transaction::update)
                  /*  create a part, an employee, and a department */
                  /*  and partially initialize them */
                  os_typespec part_typespec("part") ;
                  os_typespec employee_typespec("employee") ;
                  os_typespec department_typespec("department") ;
                  const os_database_schema &the_database_schema =
                        os_database_schema::get(*db) ;
                  const os_class_type &the_class_part = find_class( "part",
                        the_database_schema ) ;
                  const os_class_type &the_class_employee = find_class(
                              "employee", the_database_schema ) ;
                  void *a_part_ptr = ::operator new(
                        the_class_part.get_size(),
                        db,
                        &part_typespec
                  ) ;
                  void *an_emp_ptr = ::operator new(
                        the_class_employee.get_size(),
                        db, 
                        &employee_typespec
                  ) ;
                  void *a_dept_ptr = ::operator new(
                        find_class( "department",
                              the_database_schema ).get_size()
                              db,
                              &department_typespec
                  ) ;
                  os_collection &the_components_coll = 
                  os_collection::create( os_segment::of(a_part_ptr) ) ; 
                  os_collection &the_resp_engs_coll = 
                   os_collection::create( os_segment::of(a_part_ptr) ) ;
                  the_resp_engs_coll |= an_emp_ptr ;
                  os_store(
                        a_part_ptr, 
                        find_member_variable(the_class_part, "part_id"), 
                        1
                  ) ;
                  os_store(
                        a_part_ptr, 
                        find_member_variable(the_class_part, 
                              "resp_engs"), 
                        &the_resp_engs_coll
                  ) ;
                  os_store(
                        a_part_ptr, 
                        find_member_variable(the_class_part,
                              "components"), 
                        &the_components_coll
                  ) ;
                  os_store(
                        an_emp_ptr, 
                        find_member_variable(the_class_employee,
                              "dept"), 
                        a_dept_ptr
                  ) ;
                  db->create_root("part_root")->set_value( 
a_part_ptr, &part_typespec ) ;
OS_END_TXN(tx2) db->close() ; cout << "Done.\n" ; }
How the driver works
The driver performs schema installation using os_database_schema::install(). To perform installation on a database schema, you must retrieve the schema with os_database_schema::get_for_update() instead of os_database_schema::get(). The function os_database_schema::get() returns a const os_database_schema& and install requires a non-const schema&.

The driver instantiates the classes part, employee, and department by calling the global function ::operator new() without a constructor call. The function os_type::get_size() is used to supply the size_t argument to ::operator new(). The function ::os_store() is used to partially initialize the instance of part.

You can run this program and use the Browser to verify that it produces the class definitions presented on page 270.



[previous] [next]

Copyright © 1997 Object Design, Inc. All rights reserved.

Updated: 03/31/98 15:29:41