Design Patterns with Signature-Based Polymorphism: A C++ Approach
- Signature-Based Polymorphism and Interfaces
- External Polymorphism
- Signature-Based Interface
- Variations on an Interface Theme
- Conclusion
- Notes and References
This series describes an interesting but rarely mentioned technique in a C++ context: signature-based polymorphism, a more permissive variation of subtype polymorphism (see note [1] at the end of the article), usually called duck typing. Two objects having nothing in common can share an implicit interface and be commonly manipulated by such an interface—no inheritance involved. We’ll investigate two different methods of implementing such a mechanism after discussing interfaces and signatures. Intriguing enough? There is even more: an interface can enable or disable on-demand methods plus—as the title says—everything will be finally put in the context of different design patterns.
Signature-Based Polymorphism and Interfaces
Usually, an interface describes in an abstract way a contract having to be fulfilled by classes implementing that interface. This concept—widely used in OOP as one of the pillars of subtype polymorphism—has some inherent drawbacks (see notes [2] and [3] for detailed discussions):
- Coupling between the implementation hierarchy and the interface(s), forcing classes to rigidly follow the is-a approach of subtype polymorphism.
- A legacy class system is unusable in a polymorphic context without implementing the right interface. Imagine having an already designed and implemented hierarchy that has to be used in a Strategy (see note [4]), and the proper interface is missing!
- C++ doesn’t have a true interface construct; instead, it rather emulates this concept by using Abstract Base Classes (ABC) and declaring all the member functions pure virtual. This condition is orthogonal to the concept of an interface and many times superfluous. There are already languages having implemented "interface" without the implicit virtual (C#, for example), and there are some advantages attached to this approach, for example execution speed. Generally speaking, virtual functions execute slower than the non-virtual counterpart mainly because compilers cannot optimize (inline) the calls. Or to be more precise, they can be optimized in very restrictive cases when the callee’s type is known at compile time (see notes [5] and [6] for details). On the other hand, an interface (non-ABC) is completely known at compile-time, meaning that it can easily be inlined. This translates in improved performance for small virtual methods, called repeatedly (in a loop, for example).
Contrast this with signature-based polymorphism, in which a signature acts like an interface reference—an abstract type that represents and is explicitly linked to an implicit interface.
For example, the following:
struct PrinterSignature { void print(); //implementation details omitted };
declares a signature that can be used in any context in which print() can be used, regardless of the type it references:
Fax f; PrinterSignature s(&f); s.print(); // a fax prints Printer pr; s= ≺ s.print(); //a printer prints
Here, Fax and Printer are classes that have nothing in common other than a non-virtual method with the same signature, void print(), in their implicit interfaces—and even this condition can be relaxed, as we’ll see later.
A signature doesn’t have to be inherited, allowing loose coupling between interface and implementation. Actually, a signature can be seen as a "lazy interface"—it can be created and used retroactively, completely eliminating the need for careful planning of interfaces. Is it even possible to completely eliminate the need for virtual method(s) in a signature, allowing the fastest possible implementation of an interface?
Signatures in C++ were first described in note [3] and implemented as extensions of gcc compiler (see note [7]). They were not very successful, probably because they were perceived as proprietary extensions.
But pure C++ constructs are also possible by using or not using virtual functions—and two such attempts will be described in the following paragraphs.