Share

AcDbSmartObjectPointer Template

Class Hierarchy

AcDbSmartObjectPointer

C++

template <class ACDB_CLASS, getClassDescFuncType ACDB_GET_CLASS_DESC=ACDB_CLASS::desc>
class AcDbSmartObjectPointer;

File

dbobjptr2.h

Description

This class is protocol-compatible with AcDbObjectPointer and has the added capability to avoid open conflicts to access an object when given an object id, in addition to the longstanding capability to always "close" an object or at least revert it to the open state it was in prior to being assigned to the pointer.

The intent is to replace the implementation of AcDbObjectPointer with AcDbSmartObjectPointer logic in a future release. AcDbSmartObjectPointer works by NOT opening an object at all if it's open state is already what was requested, or even closing an object multiple times before opening in the desired manner. It also treats kForNotify and kForRead in the same manner, which is effectively kForRead. While this doesn't make the class foolproof, it does eliminate open conflicts cause of failure to obtain access to objects.

Some of the remaining conditions that can still cause failure to open are as follows, indicated by the associated errors:

eNotThatKindOfClass Returned when the specified object id points to an object that is not the specified class.
ePermanentlyErased Returned when the specified object id has no underlying object, whether due to undo of creation or erase/delete.
eObjectWasErased Returned if the object is erased and the openErased flag is false.
eNullObjectId Returned when the input object id is null.
eWasNotifying Returned when the specified object is currently closing from kForWrite mode, is sending notification and kForWrite mode is requested again. At that point undo recording has been done, and all reactors get to see the same object state. The workaround for this status is to record that the notification happened then wait until the AcDbObject::objectClosed(AcDbObjectId) callback is made, at which point the object can be opened for write.

Like AcDbObjectPointer, this template can also "acquire" non-database-resident (NDBR) objects and previously open objects, which it will close just once. If the DBOBJPTR_EXPOSE_PTR_REF option is enabled, then accessing the object pointer member disables the template's ability to symmetrically close or revert the object. Instead it is closed once, and the caller is responsible for it afterwards.

All members of this template have the same description and semantics of AcDbObjectPointer's corresponding members, except that open conflicts are eliminated.

The caller is reminded that it is not always good to modify an object when another caller has it open for write. However, if the caller wants to use instances of this class to obtain read or write access to objects that it may already have open and knows that the interaction is safe, then use of this class is highly recommended.

Conditions

The limitations of using AcDbSmartObjectPointer are essentially those of AcDbObjectPointer and are listed as follows:

  1. As with AcDbObjectPointer, once an object has been opened by AcDbSmartObjectPointer, ReadableAcDbObject, or WritableAcDbObject, it should not be directly opened or closed; rather one should operate through the template protocol to manage their open state. However, a nested function block can use other forms of open and close on the objects, but will have the usual potential for failure due to open conflict, unless it utilizes instances of these classes.
  2. Use of the DBOBJPRT_EXPOSE_PTR_REF-based members nullifies the guarantee that the object open state will be reverted. Once used, the object open state must be managed by the caller.
  3. Use of the AcDbObjectPointer-originated acquire() member will revert the status of the previous object (unless it was established via acquire() also), but the new object's state will not be intelligently managed; it is assumed that it will be closed exactly once.
  4. Use of the AcDbObjectPointer-originated release() member will not revert the object open state at all; this becomes the caller's responsibility.

Notes

An unfortunate side-effect of merging the automated object access into AcDbObjectPointer protocol is the additional complexity of documenting the intended use of the class. By design, the object access would be strictly determined by the instance construction and destruction, and delayed open and early close. But the additional protocol of AcDbObjectPointer to be able to switch object ids and objects, and the ability to skip object management entirely has made the description of the protocol much more complicated and confusing.

The fundamental requirement for using AcDbSmartObjectPointer, ReadableAcDbObject, and WritableAcDbObject is that the open state cannot be different at the point when these classes initiate access to the object and terminate the access, usually in the constructors and destructors. Intervening logic can open the object, as long as it also closes it. Instances of AcDbSmartObjectPointer and/or ReadableAcDbObject and WritableAcDbObject can be nested arbitrarily, as long as the nested instances release the object in reverse order of acquiring it. Failure to adhere to this requirement will result in program error.

As long as the constructor/destructor or open/close calls on these classes are exclusively used, i.e. access management is automated, multiple instances of AcDbSmartObjectPointer, ReadableAcDbObject, and WritableAcDbObject that point to a common object id X are designed to have stacking lifespans of object access, i.e. the instance last created (or to open an object) MUST be the first one destroyed (or close the object or overwrite it with an acquire()-based member). This occurs naturally when they are declared in nested code or function blocks; which is how they are intended to be used.

Although the instances are best used by declaring them on the stack, they can be allocated on the heap if you can assure the lifespan contract. Instances pointing to different objects do not have this restriction.

The destructor (or close() member) essentially reverses the object open state changes made in the constructor. One exception to this is that if an input object is already open for read within a transaction and the caller wants the object to be open for write, the object open status will be upgraded to be writable in the transaction, and will not be downgraded when the object pointer is destroyed. Instead, the closing or cancellation of the transaction itself does the work. When an object is determined to already be in the desired open mode, the template state remembers that and makes no subsequent open or close calls.

If multiple instances access the same object with different open modes, the most powerful mode applies to all pointer clients. For example, if two instances of AcDbSmartObjectPointer have the same object open, one kForRead and the other kForWrite, then both pointer clients have write access to the object. Since write access is a superset of read access, the reader client should never notice any problem.

If a pointer instance discovers that an object is already open in kForWrite mode, then the destructor or close call on that pointer instance will not generate any notification. Only the final actual close or cancel call to the object will cause the appropriate notification callbacks to be made..

Example

  1. Obtain Object Access via object id or by creating a new instance:
    // The "natural" way to intialize.
    AcDbSmartObjectPointer(AcDbObjectId   objId,
                           AcDb::OpenMode mode,
                           bool openErased = false);

    // Null constructor to be followed by open() or create() call.
    AcDbSmartObjectPointer();
    Acad::ErrorStatus open(AcDbObjectId   objId,
                           AcDb::OpenMode mode,
                           bool openErased = false);
    // Non-database-resident objects are also supported.
    Acad::ErrorStatus create();
  1. Verify that the object pointer is non-null (i.e. access succeeded):
    // If you just want a yes or no, then testing the object() pointer
    // against NULL will work.
    if (xxx.object() != 0) { /* the object is valid */ }
    // But for details on why the failure
    // occured, this function works well.
    Acad::ErrorStatus openStatus() const;
  1. Access the object by the various members provided
    // These are really 3 pairs of synonymous access members, with the operator
    // overload member implementations merely returning object().
    const ACDB_CLASS* object() const;
    ACDB_CLASS*       object();
    const ACDB_CLASS* operator->() const;
    ACDB_CLASS*       operator->();
                      operator const ACDB_CLASS*() const;
                      operator ACDB_CLASS*();
  1. Revert the Object Access.
    virtual ~AcDbSmartObjectPointer();

    // In case you can't, or don't want to, wait for the destructor
    Acad::ErrorStatus close();

As a comprehensive example:

void demoFunction(AcDbObjectId id, AcDb:OpenMode mode, bool openErased) {
    // declare the pointer, then use it as an instance of AcDbObject
    AcDbObjectPointer<AcDbEntity> sop(id, mode, true);
    if (eOk == (sop.openStatus())) {
        // Now, object() returns the object associated with id
        // if openStatus() is eOk, then the object() should be non-null
        assert(sop.object() != 0);
        assert(sop->isKindOf(AcDbEntity::desc()));
        // make read/write/notify calls on the object as desired.
    }
    // sop's destructor will revert the object's open status
}
Discouraged Usage

In contrast, here are the members which are already published in AcDbObjectPointer, but whose usage disables smart object access management, and requires more intelligence from the caller to manage the object open state.

  • Reverting access to one object and assume a new object is open:
    // After this member is invoked, the acquired object will be
    // closed once upon destruction or the close() call.  The
    // prior object is reverted or deleted or closed, depending
    // on how it was introduced to the object pointer.

    Acad::ErrorStatus acquire(ACDB_CLASS *& pObjToAcquire);
  • Forget the object and let the caller deal with slosing it:
    // pReleasedObj is set by this member, and after return
    // this pointer will no longer remember it.

    Acad::ErrorStatus release(ACDB_CLASS *& pReleasedObj);
  • DBOBJPTR_EXPOSE_PTR_REF-dependent members:
    // This constructor, and the assignment operator both invoke
    // acquire() on the new object.
    AcDbSmartObjectPointer(ACDB_CLASS *pObject);
    operator=(ACDB_CLASS *pObject);

    // Using this is the same as calling release(), and the caller
    // has the option of setting the object to something new.
    operator ACDB_CLASS* &();

Links

AcDbSmartObjectPointer Constructor, AcDbSmartObjectPointer Methods, AcDbSmartObjectPointer Operators

See Also

AcDbObjectPointer, ReadableAcDbObject, WritableAcDbObject

Previous Declaration

template <class ACDB_CLASS> class AcDbSmartObjectPointer;

Was this information helpful?