5.3. Using pointers

Note that not all combinations of the three pointer characteristics (strong/weak, stack/property, local/remote dereference) are allowed, there may be restrictions on what objects can be merged into a migration group, strong pointer properties can not reference object replicas, etc. This limitations are called pointer policies. They are either checked at compile-time or run-time.

For your convenience and to simplify pointer usage and to name valid pointer characteristics combinations, the following abbreviations were defined (read on for more complete explanation):

Table 5.1. Managed Pointer Types
Pointer instanceDescriptionFlags [a]Use primarily for
Pointer< Object >Strong stack pointer to ObjectS, LReference local object from the stack. Prevent it from being garbage collected.
WeakPointer< Object >Weak stack pointer to ObjectLReference local or remote object from the stack. Dereference locally. Use for accessing object replicas.
Remote< Interface >Remote weak stack pointer to InterfaceRReference local or remote object from the stack. Perform RPC.
PPointer< Object >Strong pointer property to ObjectP, S, LDefine a migration group of objects. Prevent referenced object from being garbage collected.
PWeakPointer< Object >Weak pointer property to ObjectP, LReference object from the same or other migration group or reference object replica.
PRemote< Interface >Remote weak pointer property to InterfaceP, RReference object from the same or other migration group or reference object replica. Perform RPC.

[a] Pointer type flags:

  P: property
  S: strong pointer, always points to local object
  L: local dereference semantics
  R: remote dereference semantics

[Note]Note

Here, Object denotes any managed object, Interface any managed interface (pure virtual managed object). The discrimination between Object and Interface is for explanatory reasons only ( properties of Object are not accessible by remote pointers), it is okay to declare Remote< Object > or PRemote< Object > though.

As can be seen from the table the Core offers three types of pointers, all in the property or stack-only variant. Since checked pointer conversions from one type to other type are possible, one need not use weak pointers with local dereference semantics at all (they can be replaced by remote pointers; remote pointers can be converted to strong pointers before dereference in order to get local dereference semantics).

The following sections describe pointer usage in detail.

5.3.1. Declaring Pointers

Managed pointers are implemented as template classes that represent the reference and are accompanied by a set of overloaded operators so that the classes could mimic the C++ pointers. Their declaration looks like this:

/* Pointers with local dereference semantics. */

template< class Type, class Cast = DefaultCast< Type > >
class Pointer;

template< class Type, class Cast = DefaultCast< Type > >
class WeakPointer;

template< class Type, class Cast = DefaultCast< Type > >
class PPointer;

template< class Type, class Cast = DefaultCast< Type > >
class PWeakPointer;

/* Pointers with remote dereference semantics (RPC). */

template< class Type >
class Remote;

template< class Type >
class PRemote;

For example Pointer< MyObject > represents strong stack pointer to MyObject. Declarations of other pointer types would look the same.

[Note]Note

Pointers are default-initialized to point to NULL_ID.

[Note]Note

We have not explained the meaning of the optional Cast parameter yet. It refers to the cast object functor that is responsible for returning of the correct C++ pointer if managed pointer is locally dereferenced. Its purpose is to convert a C++ pointer to general object to a pointer to Type. If managed class inherits Type more times, Type is ambiguous within the class hierarchy and pointer dereference will fail. In that case user has to provide own cast object that would resolve such ambiguity. This is described in the following section. However if you do not use multiple inheritance, do not inherit the same class multiple times (for example virtual inheritance is used, which is supported by Massiv) or do not want to create pointers to ambiguous Types, you can skip this section.

5.3.2. Using Cast Object

One of the problems the Core has to deal with is what component of the referenced object should be accessed if pointer is dereferenced. Note that the reference must be represented at least by ObjectId of the referenced object and Type of the referenced component. Obviously for some object instances this would not be sufficient if Type is inherited multiple times by those instances. Because of this, the reference representation was augmented to hold a Cast cast object. Cast object represents the referenced component more precisely - it identifies not only its Type but also provides code how the component should be accessed (cast from C++ pointer to general object to the C++ pointer to the requested component).

The Core provides default implementation of the cast object called Massiv::Core::DefaultCast. It works correctly as long as Type is inherited once only by the relevant class. If that is not true then the default cast object would not be able to resolve the Type ambiguity and the dereference may fail. Then user has to implement its own cast object.

Any cast object must inherit from Massiv::Core::CastObject.

class CastObject
    {
public:
    virtual VariantPointer operator()( ObjectProperty * object ) const = 0;
    /* Implements cast from ObjectProperty * to requested component. */
    virtual const std::type_info & get_target_type_info() const = 0;
    /* Returns type information of the referenced component. */
    };

For more information on how cast object should be implemented consult Massiv::Core::CastObject in the Massiv Core Programmer's Documentation.

[Note]Note

User-defined cast objects are utilized by pointers with local dereference semantics only. Pointers with remote dereference semantics operate as if default cast object was used (requested interface must be unique for the referenced object or the operations would fail).

5.3.3. Assigning To Pointers

Ability to change pointer values and reference other objects is one of the key operations pointers have to support. Managed pointers overload operator=() and allow for such changes. Any such operation results in change of the pointer state, namely ObjectId of the referenced object. The actual assignment operation is preceded by optional implicit pointer conversions and validation or verification of the assignment.

Implicit pointer conversions:

  • Pointer upcasting

    Ability to convert pointers to subclasses to pointers to superclasses. Also known as pointer subsumption. Requires compile-time check.

  • Pointer mutation

    Ability to convert pointers with one semantics to pointers with different semantics. For example implicit conversions of strong pointers to weak pointers of the same type (reference the same interface). The backward conversion requires a run-time check.

Assignment verification:

  • Either compile-time or run-time interface test

    Tests if referenced object implements requested interface. For example if one wants to change the value of a Pointer< MyClass > the compiler or system would test if the object inherits from MyClass.

    The compile-time interface test is performed when assigning a pointer of a known type to other managed pointer without an explicit cast. In this case implicit pointer conversions (in C++ pointers style, upcasts only) are allowed and this is checked by the compiler. Violation results in compile-time error.

    [Note]Note

    The compile-time check is similar to concept checks used by various C++ template libraries to test if a given template parameter supports a requested interface. If a violation is detected compile-time error in check_default_cast() will be issued.

    The run-time interface test utilizes type information of the referenced object. The test is performed when assigning an explicitly casted pointer (or arbitrary ObjectId) to other managed pointer. If type information of the referenced object is not available (or can not be interpreted by the local node), no test is performed. Pointer dereference may fail then. This can only happen when creating a reference to a remote object and system is not able to determine actual dynamic type of the referenced object.

    [Note]Note

    Type information is stored as an optional part of the object's ObjectId. It is always present if object is local but can be missing if object is remote. System can discard it if it is not able to interpret it - for example if there is no code for such object (object can not be instantiated on the local node, it is said to be alien to that node).

    In the Demo, client nodes do not know type information of server-only objects. This is related to class kinds. See Section 4.3.7, “Class Kinds”.

  • Additional pointer policy tests

    Tests if pointer with given characteristics can actually reference particular object. Always tested at run-time.

    [Note]Note

    For example strong pointers must not point to remote objects.

In the following table there are described supported assignment operations. The table shows what operands can be used as arguments to pointer's operator=() and what checks (compile-time or run-time) are performed. Run-time errors are signalled to the application by raising exceptions (see Section 5.3.11, “Exceptions”) or asserts (program logic errors, static errors).

Table 5.2. Pointer assignment variants
OperandSemanticsExceptions
Pointer with the same characteristicsCopy pointer. Always succeeds. 
Pointer with a different characteristics Copy and convert pointer. Allow implicit pointer conversions only. Assignment is validated at compile-time (upcast) and run-time (mutation, policy test). ObjectNotOnLocalSystemException, PointerPolicyViolationException
Explicitly casted pointer Copy and convert pointer. Allow any implicit pointer conversion and checked downcast. Assignment is validated at run-time (interface test, mutation, policy test). IllegalPointerConversionException, ObjectNotOnLocalSystemException, PointerPolicyViolationException
null pseudo-keywordReset pointer to point to NULL_ID. Always succeeds. 
ObjectId Create pointer to arbitrary object identified by its id. Assignment is validated at run-time (interface test, policy test). IllegalPointerConversionException, ObjectNotOnLocalSystemException, PointerPolicyViolationException
C++ pointer Create pointer from C++ pointer to local managed addressable object. Assignment is validated at run-time (policy test). Allows to assign C++ this to managed pointers. PointerPolicyViolationException

[Note]Note

Managed pointers implement the same set of constructors with similar semantics.

The use of the presented variants should be quite straightforward and is illustrated at the end of the section. Nevertheless there are some issues that need to be addressed before and will be explained now:

  • How to force an explicit cast

    Managed pointers are not casted in a C++ way using its cast operators. A special construct is provided instead. It is semantically similar to C++ dynamic_cast<> (although the pointer type to cast to is determined automatically) and is forced by calling convert() method on the pointer that should be casted. See Section 5.3.3, “Assigning To Pointers” example.

    [Note]Note

    convert() translates the pointer to a special form that will trigger the conversion when it is assigned to a pointer. The pointer type to cast to corresponds to the pointer type where the "converted pointer" is assigned to. convert() does not do any conversion until assigned as the target pointer type is unknown at the time of the call to convert().

    [Warning]Warning

    Product of convert(), the special pointer form, can not be stored by application. It must be immediatelly assigned to a managed pointer so that the conversion would be triggered.

  • Implicit pointer conversions may fail

    Unlike C++ pointer implicit conversions, implicit conversions of managed pointers can fail and throw exceptions. That is because of pointer policy testing (must be done at run-time) and implicit pointer mutation. We did not want application programmers to use explicit casts when converting between weak and strong pointers and between their stack-only and property pointer variants because the code would become unreadable. It is up to application logic to ensure that the actual operation would not fail or handle the potential exception properly.

    [Note]Note

    Implicit conversions of strong pointers to weak pointers will always succeed. See Section 5.3.10, “Pointer Policies”.

  • Assigning C++ this

    Managed pointers accept C++ this and other C++ pointers. However the pointers must point to addressable objects or the assignment would fail. For example this pointer pointing to an object instantiated on the stack can not be assigned to a managed pointer.

An example:

class X : public Object { ... };
class Y : public X { ... };
class Z : public Y { Pointer< Z > method(); };
...
Pointer< Z > Z::method()
  {
  Pointer< X > ptr_x;
  Pointer< Y > ptr_y;
  Pointer< X > other_ptr_x;
  WeakPointer< X > weak_ptr_x;

  ptr_y = CreateObject< Y >(); 1
  ptr_x = ptr_y; 2
  other_ptr_x = ptr_x;
  weak_ptr_x = ptr_x; 3
  //ptr_y = ptr_x; /* Compile-time error. Need explicit cast. */
  ptr_y = ptr_x.convert(); 4
  ptr_x = null;

  return this;
  }

1

This is how stand-alone objects are created. You should already be familiar with this construct.

2

Implicit pointer conversion (upcast).

3

Implicit pointer conversion (mutation).

4

Explicit pointer conversion (downcast). Use method convert() to force an explicit cast of the pointer.

5.3.4. Comparing Pointers

Another important feature managed pointers support is the ability to compare them. Both comparing with managed pointers and C++ pointers have been implemented, however with different semantics. When comparing two managed pointers, ObjectIds of the referenced objects are compared only, the other characteristics, mainly what components are referenced, are ignored. When comparing a managed pointer with a C++ pointer, pointer to the referenced component (as returned by the cast object) is compared with the C++ pointer.

[Note]Note

The actual implementation does not follow the wording exactly, which allows for various optimizations. However the effect is the same.

Managed pointers overload operator==() and operator!=() so that they could be compared as C++ pointers. Since the results of comparisons do not depend on the order of their operands it is sufficient to summary all the valid operand combinations in the following table. The operations will always succeed.

Table 5.3. Pointer compare variants
Operand typesSemantics
Two managed pointers with arbitrary characteristicsObjectIds are compared.
One managed pointer, one C++ pointerC++ pointers are compared.
One managed pointer and a null pseudo-keyword. Test if pointer points to NULL_ID.

[Warning]Warning

Comparing pointers with ObjectId is illegal as well as casting pointers explicitly for the purposes of comparison (one of the operands is a product of convert() method). Managed pointers with different characteristics can be compared without a cast.

[Warning]Warning

Managed pointers are compared by ObjectIds only. This can yield surprising results in multiple inheritance hierarchies.

For example if a class Foo was inherited multiple times to object A, the result of the comparison of Pointer< Foo > and C++ this pointing to the same object A would always be "not equal" as Foo would be ambiguous. However if this was converted to a managed pointer before and then compared with Pointer< Foo > the result would be "equal" as both the pointers would reference the same object.

In single inheritance hierarchies the results will be exactly the same as if C++ pointers were compared (the pointers either point to the unrelated class hierarchies, and then object ids are different, or to the same hierarchy and simple object id test is sufficient because of pointer subsumption).

5.3.5. Dereferencing Pointers

Managed pointers provide a secure way to dereference self and access components of referenced objects. When dereferenced, an active proxy [1] object, bound to the referenced object, is returned by value. Its type and implementation depends on pointer deference semantics. The proxy then delegates the access to either bound local object (and fails if there is none; proxy with local dereference semantics) or a local RPC stub that translates the access to a RPC call on the bound remote object (proxy with remote dereference semantics).

If a pointer has a local dereference semantics, the proxy tests if referenced component can be accessed locally (referenced object or its replica must be present on the local system and the component must not be ambiguous), pins the object to the local node (object is active since then) and gains full access to the object's component (as determined by pointer's cast object). When the component is no longer dereferenced the object is ready to be unpinned. Until unpinned, the object remains active and can not be deleted or can not migrate. If bound object is not present on the local node, nor its replica, the dereference will fail.

Proxy with remote semantics access methods described by IDL only. Stubs, generated for each RPC method, are responsible for translation of the local call to a RPC call. For information how RPC works see Chapter 8, Remote Procedure Call.

Pointers overload operator->() so that they could be dereferenced in usual C++ way. Attempts to dereference invalid pointers (point to NULL_ID, ambiguous or unsupported components, remote objects, etc.) are rejected and one of the following exceptions will be thrown:

  • ObjectNotOnLocalSystemException

  • IllegalPointerConversion

[Note]Note

The errors that could not be catched at assign time will probably be detected at dereference time. Remember that no interface test is performed if the type information of the referenced object is unavailable. However the object could migrate to the local node in the mean time and the interface test would be then performed at the time of the pointer dereference.

5.3.6. Special Operations

Unlike C++ pointers, managed pointers define additional member methods that can be used to query pointer state. This information can be used to test if actual pointer operation (for example an assignment to another pointer type, a pointer dereference, etc.) will succeed. Since some of the pointer functionality is turned off in release mode (see Section 5.3.13, “Differences In Debug And Release Mode”), incorrect use will either be not detected, program will simply crash or its state become inconsistent.

[Warning]Warning

Programmer must be aware of the differences and must test in advance if certain operations could be processed safely.

The following table lists some of the most useful methods. For the complete list consult the Massiv Core Programmer's Documentation.

Table 5.4. Pointer member methods
MethodSemantics
is_local()Test if the pointer can be dereferenced locally in the given context without an error. Must be rechecked whenever the context changes (the Core runs in between the two contexts, each context belongs to a different simulation tick) unless the pointer is strong. See also Section 17.1, “The Model Used By the Core”.
convert()Force an explicit cast of the pointer.
get_object_id()Get ObjectId of the referenced object.
inherits( const std::type_info & )Test if the referenced object inherits from a specified class. Throws UndefinedClassTypeException if the object is remote and its dynamic type is unknown.

An example:

class MyClass : public Object { ... };
class MyDerivedClass : public MyClass { public: PInt32 my_integer; ... };
...
WeakPointer< MyClass > ptr = ...;
try
  {
  Pointer< MyDerivedClass > ptr_derived = ptr.convert(); 1
  ptr_derived->my_integer = 2;
  }
catch( Throwable & )
  {
  }
        

1

Pointer conversion may fail because of two reasons. Either ptr refers to a remote object (thus conversion to a strong pointer is rejected) or the referenced object does not inherit from MyDerivedClass.

The same program can be rewritten so that no exceptions would be raised:

WeakPointer< MyClass > ptr = ...;
if( ptr.is_local() && ptr.inherits( typeid( MyDerivedClass ) )
  {
  Pointer< MyDerivedClass > ptr_derived = ptr.convert();
  ptr_derived->my_integer = 2;
  }

5.3.7. Migration And Replication Groups

Pointer properties can be anotated by pointer replication flags that are used for implicit definitions of migration and replication groups of objects. The flags are set by IDL ptr_repflags property attribute.

Replication group of an object is defined as a maximal set of objects reachable from that object over bidirectional pointer properties whose pointer replication flags match a given mask. Replication flags masks are "yes-no" pairs that specify what bits of pointer replication flags must be set (yes bits) and what bits must not be set (no bits). Replication groups induced by a predefined fixed [ REPMASK_MIGRATE_YES, REPMASK_MIGRATE_NO ] pointer replication flags mask are called migration groups of objects. The mask is chosen so that any replication group (induced by a different mask) is a subset of the corresponding migration group. During the enumeration process pointer properties are treated as if they were bidirectional.

Replication groups are always defined with respect to a specified mask. There is no single replication group of an object. For example the replication group of an object for the purposes of its replication from a server to a server node (induced by one mask) may be different from the replication group of the same object for the purposes of its replication from a server to a client node (induced by another mask). The masks are set globally for all allowed replication destination node types. When an object should be replicated, the Core determines type of the replication (for example “replication to server”), looks up the replication mask related to the particular replication type and enumerates the replication group of the object using the obtained mask. Specified bit of the pointer replication flags triggers replication of the referenced and the owner object to the corresponding node type.

[Note]Note

For example when replicating a Player object to a server node, not only Player but also its inventory and other helper objects (part of Player's migration group) would be replicated. However when replicating the same Player object to a client node, only the Player object and its inventory would be replicated. This is controlled by the setting of the pointer replication flags.

Migration groups are replication groups with a predefined REPFLAGS_MIGRATE pointer replication flags bit set. If this bit is on then the referenced object will always be (must be) present on the same node as the object that owns the pointer and the pointer will implicitly define a migration group of objects. It is also said that the pointer triggers the migration of the referenced object or the owner object if the other object migrates. Such a pointer need not be strong. However the Core automatically sets this bit for strong pointer properties.

[Note]Note

Pointer properties that define replication/migration groups are treated bidirectionally. This is needed to ensure that strong pointers always reference local objects, for example. Otherwise if B migrated to an another node, the strong pointer from A to B would be pointing to a remote object. Although this makes implementation of ObjectPointer and group enumeration more complex this proved to be very useful. For example Player object can reference its Inventory object by a single strong pointer and both migration groups of Player and Inventory are the same (Inventory need not reference Player). Together with implicit replication/migration group definitions this is one of the key properties that make the Massiv unique.

Confused? See the example:

class Inventory : public Object { ... }; /* All ptr_repflags = 0 */
class Account : public Object { ... };   /* All ptr_repflags = 0 */

class MapSector : public Object
  {
public:
  ...
  /* ptr_repflags = 0 */
  PRemote< MapSector > neighbor[ 8 ];
  };

class Player : public Object
  {
public:
  ...
  /* ptr_repflags = MIGRATE | SERVER_BIT | STANDARD_CLIENT_BIT */
  PPointer< Inventory > inventory;

  /* ptr_repflags = MIGRATE | SERVER_BIT */
  PPointer< Account >   account;

  /* ptr_repflags = MIGRATE */
  PPointer< MapSector > sector;
  };
...
Pointer< Player > player = ...;
Pointer< Inventory > inventory = ...;
Pointer< Account > account = ...;
Pointer< MapSector > sector = ...;

player->inventory = inventory;
player->account = account;
player->sector = sector;

Suppose that we have named pointer replication flags bits so that MIGRATE triggers object migration (defines a migration group), SERVER_BIT triggers replication to server nodes and STANDARD_CLIENT_BIT replication to standard client nodes. Inventory, Account and MapSector objects contain weak pointers with ptr_repflags = 0 only, then:

  • Corresponding Player, Inventory, Account and MapSector objects form the same migration group. If more players are linked to the same sector all the Players (and their Accounts and Inventories) will implicitly belong to the same (single) migration group. On the other side, different MapSector objects belong to different migration groups.

  • Replication group of the Player enumerated due to replication to a server node will contain the Player object and its Account and Inventory objects. The MapSector and the other Player, Account and Inventory objects (the rest of the migration group) will not be replicated to server nodes.

  • Replication group of the Player enumerated due to replication to a standard client node will contain only the Player object and its Inventory object.

[Note]Note

In the example we were assigning strong pointers to the Player object which caused the merging of the migration groups resulting in the creation of a single migration group containing all the involved objects. First, the migration group of the Inventory object and the Player object was merged, then the migration group of the Account object was joined, etc. Since we were working with strong pointers all the groups had been local. If a remote migration group should have been merged, our Player object would have to migrate to the remote group (taking its current migration group with self) so that the join could have been finished on the remote node. See Section 6.5, “Requesting Migration”.

[Note]Note

Right now you might be confused. How do I assign ptr_repflags to a pointer? What values can be assignmed to ptr_repflags? The answer is: ptr_repflags is an attribute of pointer properties, it's value is specified in IDL. For more information see Section 7.5, “Replication-related IDL Attributes” and Section 10.8, “Attributes”.

5.3.8. Pointers To Forward Declared Classes

When creating recursive data structures, one has to use pointers to forward declared classes in order to avoid cyclic header inclusion. Pointer implementation allows to declare managed pointers to forward declared classes. However at the time of the pointer use (for example dereference), the referenced class must have already been seen by the compiler. This requires to organize source code in such a way that headers contain class declarations only and the full definition, including method bodies, is provided in .cpp files.

Header file:

...
class A;
class B;

class A : public Object
  {
public:
  void set( Pointer< B > ptr );
  PPointer< B > ptr_b;
  ...
  };

class B : public Object
  {
public:
  void set( Pointer< A > ptr );
  PPointer< A > ptr_a;
  ...
  };

CPP file:

...
void A::set( Pointer< B > ptr )
  {
  ptr_b = ptr;
  }

void B::set( Pointer< A > ptr )
  {
  ptr_a = ptr;
  }

[Note]Note

Classes must be forward-declared in the IDL too. See Section 10.9.1, “Forward Declaration”.

5.3.9. Pointer Replicas, Pointers To Object Replicas

Pointer property semantics changes if the property is actually a replica. In that case the object that owns the property is also a replica. This means that pointer's static characteristics will be partially ignored. In particular all pointer property replicas are considered weak regardless to their static characteristics. That is because not replicated portions of migration groups, thus remote, can be referenced from the replicated parts by originally strong pointers.

[Note]Note

In the example above the Player object replicated to a standard client node would have its pointers to Account and MapSector pointing to the original (remote) objects and the objects would not be replicated to that node.

[Warning]Warning

This has a consequence that the conversion of PPointer< Object > to Pointer< Object > can fail if the property is a pointer replica since we are effectively converting a weak pointer property to a strong stack pointer.

The other issue is how object replicas can be accessed by other non-replica pointers (either regular stack pointers or non-replica pointer properties). Let us note that object replicas have the same object id as the original objects and are accessible by pointers with local dereference semantics only. There are never the original object and its replica present on the same node at the same time (for more information see Section 7.4, “Accessing Object Replicas”). Obviously strong pointer properties can not reference object replicas because the corresponding original objects are remote. Also strong stack pointers can not reference object replicas (such references would prevent the Core from updating strong-referenced replicas and could block incoming migrations too, see Section 7.10, “Allowed and Illegal Operations”). However they can be referenced by weak pointers of any other characteristics.

[Warning]Warning

Calling a SRPC from within a replica (if an object replica is active) is forbidden as it would prevent the Core from updating the replica.

5.3.10. Pointer Policies

Pointer policies state what pointers, with given characteristics, can reference particular objects and what the Core guarantees if such a pointer exists. The following object and pointer properties are investigated:

  • What is the dynamic type of the object?

  • Is the object local or remote?

  • If the object is local is it a replica?

  • Is the pointer a replica?

Although most of the policies have already been mentioned in the previous sections they have been summarized in the following lists and tables.

Policies applicable to both stack pointers and pointer properties:

  • Object's dynamic type is utilized to validate pointer operations. Can not create pointers to objects that do not support requested interfaces, can not dereference pointers to ambiguous interfaces

    The violation is signalled to the application by raising IllegalPointerConversionException exception, either at assign time (this is the interface test already described at Section 5.3.3, “Assigning To Pointers”) or dereference time.

  • Strong pointers must reference local objects only

    Assignment of a remote object to a strong pointer results in ObjectNotOnLocalSystemException exception. The Core guarantees that if the assignment succeeded at some time, the referenced component was unambiguous and the referenced object was not explicitly deleted by user in the meantime, then the pointer dereference at a later time would always succeed. The concept of migration groups ensures that this policy is not violated when objects migrate.

    In particular objects referenced by strong-stack pointers can not migrate until the pointers are released. If an object A migrates all the objects that reference A by strong pointer properties or are referenced from A by strong pointer properties will migrate together with the object.

    [Note]Note

    This feature is crucial. It allows to access objects without worrying if the objects are still local.

    class X : public Object { .... void f(); };
    class Y : public Object { .... void g(); };
    ...
    Pointer< X > strong_ptr = ...;
    WeakPointer< X > weak_ptr = ...;
    Remote< Y > remote_ptr = ...;
    
    strong_ptr->f();
    
    if( weak_ptr.is_local() )
      {
      weak_ptr->f();
      ...
      remote_ptr->sync_g(); 1
      ...
      strong_ptr->f();
      weak_ptr->f(); 2
      }

    1

    This performs a SRPC call on a remote object. The Core runs meanwhile and the object referenced by weak_ptr can migrate out of the local node.

    2

    The dereference may fail as the referenced object can no longer be local. See threading model Section 17.2, “When the Core Runs” to get information on when the Core can run.

    The above example could have been rewritten so that the racing conditions would have been avoided:

    if( weak_ptr.is_local() )
      {
      Pointer< X > ptr = weak_ptr; 1
      ...
      remote_ptr->sync_g();
      ...
      strong_ptr->f();
      ptr->f();  
      }

    1

    This will fail if weak_ptr points to an object replica. See the following policy.

  • Strong pointers can not reference object replicas

    Object replicas can be referenced by weak pointers only . This applies to stack pointers too. If strong stack pointers to object replicas were allowed the Core would have to freeze updates of the referenced replicas in order to ensure consistency and fulfil the requirement that the replicas would not be deleted while still strong-stack-referenced. Also incoming migrations turning object replicas to regular non-replica objects would have to be blocked. In the overall the system would be stalled.

    [Warning]Warning

    When using weak pointers with local dereference semantics one has to be careful when the pointers are dereferenced. For example if a previous dereference succeeded, a later dereference may fail if the Core ran in between the two dereferences. This is the semantics of weak pointers and a neccessary feature that allows to implement an effecient replication model. For more information see threading (execution) model Section 17.3, “What Happens When the Core Runs”.

  • Pointers to objects allocated on the stack or member objects owned by other objects can not be created

    These objects are not addressable because they do not have an ObjectId.

The following policies are applicable to pointer properties only. They ensure migration groups consistency. The policies are violated due to program logic errors only and they are signalled to the application through a generic PointerPolicyViolationException exception:

  • Pointer properties with REPFLAGS_MIGRATE pointer replication flags bit set can not reference remote objects

    Such pointers define migration groups of objects. Strong pointer properties are this kind of pointers.

  • Pointer properties with REPFLAGS_MIGRATE pointer replication flags bit set can not point to object replicas

    The original objects are remote and have the same object ids as their replicas.

  • Pointer properties with REPFLAGS_MIGRATE pointer replication flags bit set can not be used to link archivable and non-archivable migration groups

    If this was allowed we would get a single migration group of objects containing both archivable and non-archivable objects.

  • Pointer property replicas are always weak

    Static pointer characteristics is overloaded if the pointer is a replica.

Most of the policies take effect if doing implicit pointer conversions when pointer characteristics changes (pointer mutation). The behavior is summarized in the following table. It can be seen that conversions to weak pointers always succeed (the plus signs) and the other conversions fail if the referenced object is remote or an object replica :

[Note]Note

The following table ommits several kinds of pointers. It is because a mutation to them will always succeed. They are: WeakPointer< X >, Remote< X >, PWeakPointer< X > and PRemote< X >.

Table 5.5. Pointer mutation
From\ToPointer< X >PPointer< X >
Pointer< X >++
WeakPointer< X >Fails if points to a remote object or a replicaFails if points to a remote object or a replica
Remote< X >Fails if points to a remote object or a replicaFails if points to a remote object or a replica
PPointer< X >May fail if from pointer replica pointing to a remote object or will fail if points to a replicaWill fail if from pointer replica pointing to a remote object or an object replica
PWeakPointer< X >Fails if points to a remote object or a replicaFails if points to a remote object or a replica
PRemote< X >Fails if points to a remote object or a replicaFails if points to a remote object or a replica

There is always a way to check if certain operation is correct so that the policy violation exception could be avoided. The check can be done on the application level by using these methods:

  • local_pointer.is_local()

    Tests if the pointer with local dereference semantics can be dereferenced. See Section 5.3.6, “Special Operations”.

  • local_pointer_property.is_replica()

    Tests if the pointer property is a replica.

  • local_pointer->is_replica()

    Tests if the pointer points to an object replica (assuming that the pointer can be dereferenced locally).

  • local_pointer.inherits( ... )

    Tests if the referenced object implements a requested interface.

5.3.11. Exceptions

Pointer subsystem throws managed exceptions if an incorrect use of a pointer is detected. If an exception is thrown, the state of the involved pointer is rollbacked to a previous consistent state. The following table describes the exceptions from the point of view of the pointer subsystem, the exceptions can be used by other subsystems as well:

Table 5.6. Exceptions thrown by the pointer subsystem
ExceptionWhyWhen
CanNotAccessObjectExceptionOperation on an object could not be processed.Dereferencing a NULL_ID pointer.
IllegalPointerConversionExceptionObject does not implement the requested interface, or the interface is ambiguous. See Section 5.3.2, “Using Cast Object”.Assigning or dereferencing pointers.
ObjectNotOnLocalSystemExceptionTrying to operate on a remote object.Dereferencing a pointer with local dereference semantics pointing to a remote object. Assigning a remote object to a strong pointer.
PointerPolicyViolationExceptionPointer property specific policy was violated.Assigning a pointer.
RemoteCallFailedExceptionAttempt to issue a remote procedure call failed.Processing a RPC request.
UndefinedClassTypeExceptionObject's dynamic type is unknown.Requesting a type information of a remote object.

5.3.12. Differences From C++ Pointers

This chapter summarizes the basic differences between C++ pointers and managed pointers. As could have been seen, managed pointers try to imitate C++ pointers as authentically as possible, but also add non-trivial extensions such as type and reference safety or implicit migration and replication group definition. Some of the features already supported by C++ pointers are implemented by managed pointers differently (or not at all) and that's why this chapter is present:

  • Pointer operations automatically throw exceptions if an illegal operation is detected

  • To force an explicit conversion, when an implicit conversion is not available, use convert() method. IllegalPointerConversionException will be thrown if type information on the involved object is available and the conversion is illegal. Type information might not be available if the object is remote and of unsupported class kind (see Section 4.3.7, “Class Kinds”). In that case no test is performed! The potential error should be detected when the invalid pointer is used to invoke a remote call on that object.

  • Use null pseudo-keyword to assign NULL_ID values to a pointer or compare the pointer with NULL_ID

  • Pointers to non-addressable objects (allocated on the stack, member objects) can not be created

5.3.13. Differences In Debug And Release Mode

Full pointer policy testing can be quite an expensive task. However most of the policies that are checked at run-time are intended to catch program logic errors. This means that the policy violation is often systematic and once the program has been debugged and it has been verified that the policies are no longer violated, the checks can be turned off and gain significant program speed up.

If the application is compiled in debug mode all the tests that can be performed locally are turned on. This includes full pointer policy testing. When compiled in release mode the following tests are turned off or are different:

  • No null or ambiguous pointer dereference test

    An attempt to dereference such a pointer is functionally equivalent to the dereference of a C++ NULL pointer.

  • Limited pointer policy testing

    The checking of the policies whose violation would be signalled by a generic PointerPolicyViolationException exception is turned off. These policies ensure consistent use of pointers with respect to the definition of migration groups and the use of object replicas.

[Warning]Warning

Application programmer must be aware of the differences. The program is considered ill formed if it violates the policies that are checked in debug mode only. Programmer can check the policy on the application level if needed.

5.3.14. Advanced Techniques

Managed pointers can be combined with C++ references pointing to object members if extra attention is given. These techniques are considered advanced as one could get along with managed pointers only. For example in Java you can not create a reference to an object member.

[Warning]Warning

In Massiv it is okay to create and pass short-time aliasing C++ references pointing to local members to other local methods as far as the object that owns the referenced members is (and will remain) active. Such references must not be stored (kept) by the callee. Similarly, C++ references to data allocated on the stack can be passed to local methods if they are released when the callee exits. Simple standard scoping rules will help you to understand this.

void print( const Property & p )
  {
  std::cout << p;
  }

void MyObject::method()
  { 1
  std::cout << *this;
  print( this->my_property ); 2
  }

/*
const Property & MyObject::get_my_property() const
  {
  return this->my_property; 3
  }
*/
1

Suppose that MyObject instance has been called through a managed pointer. MyObject instance has already been pinned when program got to this point. Application can now manipulate with MyObject instance as usually.

2

Pass my_property to print() by C++ reference, print() must forget the reference at its exit.

3

Wrong! Can not export a C++ reference to an object that won't be active any more.



[1] Proxy objects serve as local gates to referenced objects. Any access to a referenced object is intercepted by a proxy object and can be translated to a remote procedure call, for example. These object proxies are called active as they are returned by value and they are deleted as soon as the object is no longer dereferenced (local proxy can be used to measure how long the object has been active or whether it is active now). This is transparent to the application.