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.
Private members
Declaring certain members of the class as private, external access and calling of these members is prevented at compile time. Private members are therefore considered ‘implementation’, and public members are considered ‘interface’. Drawback: The internal mechanism of the implementation is transparent and open to public (as it is included into the .h header file). The result is an interface definition that appears cumbersome, complex, and difficult to read and follow.
Virtual methods
Declaring certain methods as virtual allows the inherited classes to override such methods, and the call destination to be established at run time, enabling polymorphism. Encapsulation is usually ensured by defining a base, virtual class, the ‘interface’, and then subclassing it by an ‘implementation’. Drawback: a virtual method call destination is established at run time, which generates overhead.
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
Minimum overhead due to static linking. Many compilers are now capable of inlining such functions, thus even increasing the performance. Strict separation of interface from implementation. When an interface .h header file is given, only definitions regarding interface may be included, thus making it easier to read. Integrating interface definitions with basic documentation is also made easier. Additional freedom in implementation architecture. Separating it completely from the interface, interconnections between objects on the low level may change from implementation to implementation without any influence on the interface. The ‘interface’ layer (eg. tMyClass functions) provides space for such things as diagnostics, error checking and user-friendliness functions without any influence on the raw, low-level implementation, so that may remain focused on execution and heavy labor code.
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);
//...
}