Simplified Strict Encapsulation

This method may be used to simplify interfaces to functionalities in c++ libraries. It offers a clear and transparent separation of implementation specifics (which may vary from platform to platform or version to version) from strictly defined and maintained interfaces.


This method relies on the fact that object method calls, when static (not virtual), are generated at compile time, based upon the declared type of an object, not the type of the object itself. This may be illustrated by the example:

class A {
  public:
    void method0(int a);
    virtual void method1(int a);
};
      
A  myObject;
A* myObjectPtr;
A& myObjectRef;
int a;
myObject.method0(a);      ----> A::method0(this, a)                      //1)
myObject.method1(a);      ----> A::method1(this, a)                      //1)
myObjectPtr->method0(a);  ----> A::method0(this, a)                      //1)
myObjectPtr->method1(a);  ----> (myObjectPtr->virtual_table[0])(this, a) //2)
myObjectRef.method0(a);   ----> A::method0(this, a)                      //1)
myObjectRef.method1(a);   ----> (myObjectRef.virtual_table[0])(this, a)  //2)

1) call destination is determined upon compile time, and the actual contents of pointer or reference have no influence.

2) call destination is determined upon run time, and depends on actual contents of pointer or variable represent.

Encapsulation: separating interface from implementation

Two alternatives are regularly used in c++ for achieving encapsulation: private members and virtual methods.

The new proposition for encapsulation is as follows:

The interface and implementation are defined as separate structures, eg. “tMyClass” (interface) and “tmyclass” (implementation).

An object is then created with a specific new function, and freed with a specific release function:

tMyClass*         new_MyClass(...);   //optional parameters
void              release(tMyClass* object);

In certain cases, the library may not allow objects to be created and/or released freely. Sometimes, other interfaces may be used to maintain such objects, eg:

tResource*        tResourceDB::access(const char* resname);
void              tResourceDB::unlock(tResource* res);

An interface structure is defined as public, base structure with no members, only the interface methods listed, eg:

struct            tResourceDB {
  tResource*      access(const char* resname);
  void            unlock(tResource* res);
  int             number_of_resources();
  const char*     resource_name(int index);
};
      
tResourceDB*      new_ResourceDB(...);   //optional parameters
void              release(tResourceDB* object);

An implementation structure is defined as internal structure or class of any kind (even virtual, if necessary), with its members usually declared public so the rest of the library may access it when objects are interconnected. Eg:

struct            tresourcedb {
  tresource*      resarray;
  int             rescount;
       
  int             find_resource(const char* name);
  bool            load_from_file(const char* fname);
  #ifdef IMPL_2
    void          lock(tresource* res);
    void          unlock(tresource* res);
  #endif
};

When an object is created or otherwise accessed, the implementation object is actually generated, then type cast to interface structure. When it is released or used through the interface, the reverse is done:

tResourceDB*      new_ResourceDB(const char* fname)
  {
    tresourcedb* newdb = new tresourcedb;
    if (newdb) {
      if (!newdb->load_from_file(fname)) {
        delete newdb;
        return nul;
      }
    }
    return (tResourceDB*)newdb;
  }
        
void              release(tResourceDB* db)
  {
    if (db) delete (tresourcedb*)db;
  }

To call an implementation function from within an interface function, or to access its members, a macro can be defined to convert this pointer to proper type.

#define           self ((tresourcedb*)this)
          
static            tresourcedb defaultdb;
static            tResourceDB& defaultDB = *(tResourceDB*)(&defaultdb);
         
tResource*        tResourceDB::access(const char* resname)        
  {
    if (!this) return defaultDB.access(resname);
    int fnd = self->find_resource(resname);
    if (fnd < 0) return nul;
    tresource* res = self->resarray[fnd];
    #ifdef IMPL_2
      self->lock(res);
    #else        
      res->lock();
    #endif
    return (tResource*)res;              
  }
        
void              tResourceDB::unlock(tResource* r)
  {
    if (!this) return defaultDB.unlock(r);
    tresource* res = (tresource*)r;
    #ifdef IMPL_2
      self->unlock(res);
    #else
      res->unlock();
    #endif
  }

Benefits:

Example

void test() {
  tResourceDB* db = new_ResourceDB("database.txt");
  tResource*   res = db->access("test"); //normally unsafe, legal with encapsulation
  tString      txt = res->description(); //normally unsafe, legal with encapsulation
  db->unlock(res);                       //normally unsafe, legal with encapsulation
  release(db);
  //...
}