Sponsored Link •
|
Summary
At Matthew Wilson's behest, I have attempted to further explain my rationale behind separate extension classes, and contract verification classes. It turns out they fit very nicely in a design pattern which Matthew refers to as a bolt-in.
Advertisement
|
Bolt-ins are template classes with the following characteristics:In all frankness I consider that to be a somewhat fluffy defintion, and I would have stated it more concisely as follows:
- They derive, usually publicly, from their primary parameterizing type.
- They accomodate the polymorphic nature of their primary parameterizing types. Usually they also adhere to the nature, but this is not always the case, and they may define virtual methods of their own, in addition to overriding those defined by the primary parameterizing type.
- They may increase the footprint of the primary parameterizing type by the definitiion of member variables, virtual functions, and additional inheritance from nonempty types.
- Imperfect C++, page 375, by Matthew Wilson, published by Addison-Wesley, 2005
Bolt-ins are template classes which inherit from their primary parameterizing type, while accomodating its polymorphic nature.The other parts of the definition are superflous, and do not add to the definition. They appear to be solely intended to distinguish bolt-ins from veneers. Matthew goes on to disintiguish a bolt-in primarily by its role:
... bolt-ins are concerned with significantly changing or completing the behavioural nature of (often partially defined) types. [...] The primary purpose of a bolt-in is to add or enhance functionality.Unfortunately I find it difficult to identify specifically when I am using a bolt-in and when I am using plain old template parameter inheritance. However I will use the term bolt-in for the time being. I would however like to encourage Matthew to further refine the definitions of bolt-ins, veneers and attempt to formalize the other forms of template parameter inheritance patterns. On that note, Joel de Guzman suggested the term Parametric Base Class Pattern (PBCP) for all the forms of template parameter inheritance.- Imperfect C++, page 375, by Matthew Wilson, published by Addison-Wesley, 2005
Following the principle of separation of concerns has the benefit of reduction of code coupling, and improvement of code cohesion. These are both well-known good software engineering practices. The advantages are that following these principles more naturally leads to more maintainable and reusable code.
Bolt-ins can be used to enforce separation of concerns, and to reduce code coupling.
class SimpleClass { public: Init() { // do initialization } void DoSomething() { // precondition: object must be initialized // do something } }There is here an implied precondition to calling DoSomething() which is that the member function Init() must have been previously called on a particular instance. A common and somewhat naive approach to assuring the precondition is done by introducing a member variable:
class SimpleClass { public: SimpleClass() : mbInit(false) { } Init() { // do initialization mbInit = true; } void DoSomething() { assert(mbInit); // do something } private: bool mbInit; }Notice that mbInit is solely used for verifying that the class is used correctly. The problem is that it probably will only be needed during debug builds. The naive solution then would be:
class SimpleClass { public: SimpleClass() #ifdef _DEBUG : mbInit(false) #endif { } Init() { #ifdef _DEBUG // do initialization mbInit = true; #endif } void DoSomething() { assert(mbInit); // do something } private: #ifdef _DEBUG bool mbInit; #endif }Needlessly to say that is a mess. The problem results from a failure to separate concerns modularly. One improved solution is to use inheritance:
class SimpleClass_with_contract : SimpleClass_impl { public: void SimpleClass_with_contract() : mbInit(false) { } void Init() { SimpleClass_impl::Init(); mbInit = true; } void DoSomething() { assert(mbInit); SimpleClass_impl::DoSomething(); } private: bool mbInit; }; class SimpleClass_impl { public: void Init() { // do initialization } void DoSomething() { // do something } }; #ifdef _DEBUG typedef SimpleClass_with_contract SimpleClass; #else typedef SimpleClass_impl SimpleClass; #endifThis solution is good, but the contract verification class is not reusable because it is coupled with that particular implementation. This arises because we have only partially separated the concern of contract verification. Ideally I want something reusable, so I will use a class which inherits from its template parameters (a bolt-in).
template<typename Impl_T> class Simple_contract : Impl_T { public: void Simple_contract() : mbInit(false) { } void Init() { Impl_T::Init(); mbInit = true; } void DoSomething() { assert(mbInit); Impl_T::DoSomething(); } private: bool mbInit; }; class SimpleClass_impl { public: void Init() { // do initialization } void DoSomething() { // do something } }; #ifdef _DEBUG struct SimpleClass : Simple_contract<SimpleClass> { }; #else struct SimpleClass : SimpleClass_impl { }; #endifThis new contract verification class can now be used with other classes with similar interfaces:
class AnotherSimpleClass_impl { public: void Init() { // do initialization } void DoSomething() { // do something } }; #ifdef _DEBUG struct AnotherSimpleClass : Simple_contract<AnotherSimpleClass> { }; #else typedef AnotherSimpleClass_impl AnotherSimpleClass; #endifThis demonstrates how through the diligent practice of separation of concerns we can arrive at code which is more reusable.
class MyString_impl { public: void SetCount(int n) { m.resize(n); } void SetChar(int n, char c) { m[n] = c; } char GetChar(int n) const { return m[n]; } int Count() { return m.size(); } private: std::vector<char> m; };This class is too minimalist to be of much use as-is, however with this basic implementation you can implement virtually every other concievable string member function. By writing the derived functions within a bolt-in, I can reuse the derived set of memember functions on any class which matches the code interface. For instance consider the following bolt-in:
template<typename Impl_T> class String_ext : public Impl_T { public: void Concat(const Impl_T& x) { int n = Count(); SetCount(n + x.Count()); for (int i=n; < Count(); i++) { SetChar(i, x.GetChar(i-n)); } } void Assign(const Impl_T& x) { SetCount(x.Count()); for (int i=0; i < Count(); i++) { SetChar(i, x.GetChar()); } } Impl_T SubString(int n, int cnt) const { Impl_T s; s.SetCount(cnt); for (int i=0; i < cnt; i++) { s.SetChar(i, GetChar(i + n)); } return s; } // and so on and so forth };This extension class can be bolted on to any compatible class implementation as simply as:
typedef String_ext<MyString_impl> MyString;The advantage of doing this is that the extension class can be reused on any class matching the core interface. This also reduces the coupling between extension functions and the implementation to a set of four functions. Refactoring and debugging the code as a result becomes much easier.
We can also define a contract verification class for MyString as follows:
template<typename Impl_T> class String_contract : public Impl_T { public: void SetChar(int n, char c) { assert(n < Count()); Impl_T::SetChar(n); assert(GetChar(n) == c); } char GetChar(int n) const { assert(n < Count()); return Impl_T::GetChar(n); } }Tying everything together gives us:
#ifdef _DEBUG typedef String_ext<String_contract<MyString_impl> > MyString; #else typedef String_ext<MyString_impl> MyString; #endif
One solution I am aware of is to use template constructors. This is not a perfect solution though.
My currently preferred solution to this design conundrum is to forego initializing construction entirely within my libraries. A rather drastic and contentious measure, but perhaps one which may pay off, because it opens the door more widely to sophisticated usage of template parameter inheritance techniques such as bolt-ins, contract verificiation classes, extensions classes, and more. Some debate on the merits of this approach can be found in the comments of my earlier blog entry Two Stage Construction in C++ versus Initializing Constructors
Have an opinion? Readers have already posted 16 comments about this weblog entry. Why not add yours?
If you'd like to be notified whenever Christopher Diggins adds a new entry to his weblog, subscribe to his RSS feed.
Christopher Diggins is a software developer and freelance writer. Christopher loves programming, but is eternally frustrated by the shortcomings of modern programming languages. As would any reasonable person in his shoes, he decided to quit his day job to write his own ( www.heron-language.com ). Christopher is the co-author of the C++ Cookbook from O'Reilly. Christopher can be reached through his home page at www.cdiggins.com. |
Sponsored Links
|