Following on from the article on Adapter patterns (Read more here) I’ve decided to explore the memory models of each of these patterns.
We’ll start with the simple case of a UtilityProvider class being a simple class with no virtual methods. Then we’ll look at what happens when the UtilityProvider has virtual functions added.
To flesh out the memory models I’ve added (arbitrary) data to both the UtilityProvider class and its adapters.
These memory models are based on the IAR Embedded Workbench C++ compiler. It’s a fairly typical compiler for embedded systems development. For simplicity I’m ignoring padding issues; compilers will usually pad to the next word boundary, so assume what you see is word-aligned objects.
The Object Adapter
A quick reminder of the Object Adapter pattern. The ObjectAdapter class realises the IService interface and forwards on all calls to the encapsulated UtilityProvider object. The Utility provider object can be created by the client and passed in the adapter, allocated on the free store by the adapter, or stored as a nested (composite) object.
Figure 1 – The Object Adapter pattern
Object Adapter memory model
If the UtilityProvider is created outside the adapter (or on the free store) the memory model is as shown in Figure 2. Note the ObjectAdapter class has a vtable pointer since it implements the IService interface. The de-facto model is to place the vtable pointer as the first element in the class (in other words at ‘this’)
Figure 2 – ObjectAdapter with a reference to the UtilityProvider object
If the UtilityProvider object is a composite object the memory model changes (Figure 3). Note, the ObjectAdapter vtable pointer is still the first element in the object.
Figure 3 – ObjectAdapter with nested UtilityProvider
The Class Adapter pattern
A Class Adapter realises the client-required interface but privately inherits from the UtilityProivder class (hiding its methods) – See Figure 4
Figure 4 – The Class Adapter pattern
Class Adapter memory model
The memory model for inheritance in C++ constructs a base class object as part of the derived class, so we should expect a memory model similar to that of the composite-object Object Adapter. In fact, it is identical( Figure 5)
Adding virtual functions to the UtilityProvider.
Suppose our UtilityProvider class has virtual functions (for example Figure 6). How does this affect the memory model?
///////////////////////////////////////////////////////////////////// // class UtilityProvider { public: UtilityProvider(); void func1(); void func2(); void func3(); void func4(); virtual void func5(); // Requires a vtable. protected: void helperFunction(); private: // Private data. };
Figure 6 – UtilityProvider with virtual functions
The Object Adapter memory model
When the UtilityProvider class has virtual functions it gets its own vtable and vtable pointer (Figure 7). It’s worth noting the order in which the memory is allocated depends on the order of declaration in the class definition. In this example I’ve put the UtilityProvider object as the first declaration; before any other data.
Note also that the ObjectAdapter’s vtable pointer is still the first element in the object.
Figure 7 – Object Adapter with virtual UtilityProvider
Class Adapter memory model
In a single-inheritance model the derived class shares its vtable pointer with the base class (that’s the basis of polymorphism. In the case of the Class Adapter model this isn’t the case. Both the ClassAdapter and UtilityProvider retain their own vtable pointers. (This allows the ClassAdapter to have a virtual function with the same signature as the UtilityProvider, and still call the UtilityProvider’s virtual function. If they shared the same vtable pointer you could end up with an infinite recursive call)
There is also a difference on memory layout depending on whether the IService interface is the Primary Base Class and the UtilityProvider is a Secondary Base Class( Figure 9); or vice versa (Figure 10)
You should always favour having the Interface as the Primary Base Class. For more information on why see this white paper.
///////////////////////////////////////////////////////////////////// // class ClassAdapter : public IService, // Primary Base Class private UtilityProvider // Secondary Base Class { // .... }; ///////////////////////////////////////////////////////////////////// // class ClassAdapter : private UtilityProvider, // Primary Base Class public IService // Secondary Base Class { // .... };
Figure 8 – ClassAdapter definitions with different PBC and SBC
Figure 9 – ClassAdapter with IService as Primary Base Class
Figure 10 – ClassAdapter with UtilityProvider as Primary Base Class
Summary
The memory models of the Adapter patter are all very similar. Remember when programming these patterns the associated overheads:
- Extra code space for the vtable(s)
- An extra vtable pointer for each object with virtual functions
- The overhead of virtual function calls (note the double overhead if you are calling a virtual function on the original class!)
- Practice makes perfect, part 3 – Idiomatic kata - February 27, 2020
- Practice makes perfect, part 2– foundation kata - February 13, 2020
- Practice makes perfect, part 1 – Code kata - January 30, 2020
Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.
He specialises in C++, UML, software modelling, Systems Engineering and process development.
Both articles very informative.
Starting with the abstract pattern and its "why"...to its relationship w/the moire concrete is very helpful.
Thank you.