Feb 16

Automatic Adaptation in C++

Category: C++, Programming

The adapter pattern is used to implement an interface required by one library in terms of an object declared in another. It’s a useful pattern, but implementing it can be a pain. You have to manually adapt objects as you get them and then you have to remember to clean up the adapter objects when you are done. Here is a way to have the compiler do it all for you in C++.

This article assumes you are familiar with the concept of the adapter pattern and with the concept of a smart pointer. We have two libraries: lib1 and lib2. lib1 contains some object type AObject and lib2 implements some nice generic algorithm in terms of an abstract class called Interface. I’m writing an application and I’d like to apply lib2’s algorithm to lib1’s AObject objects. To use the adapter pattern I define a new class called Wrapper which takes a pointer to an object of type AObject and extends class Interface as follows:

/*some object in lib1*/
namespace lib1{
  class AObject{
  ...
  };
}
/*some other object type in lib2*/
namespace lib2{
  class Interface{
  ...
  };
}
class Wrapper: public Interface{
public:
  Wrapper(shared_ptr<AObject> ptr);
  ...
};

What I’m proposing is to make the following work:

shared_ptr<Interface> wrap_ptr(Interface *, shared_ptr<AObject> ptr){
  return shared_ptr<Interface>(new Wrapper(ptr));
}
void method(shared_ptr<Interface> ptr){
  ...
}
int main(){
    shared_ptr<AObject> a_object = shared_ptr<AObject>(new A());
    method(a_object); //automatically wrapped as Interface on call.
}

So how do we do it? We overload the conversion operator for our smart pointer. There are two parts to this: 1) If the pointer type we are trying to convert to is a super class of the type we are converting from we should just cast the pointer 2) otherwise we should try to use a wrap_ptr function (if this is not defined by the user then you will get a compiler error).

The following mechanism can be used to determine if one type is derived from another (refer to boost for details on why)

/**mechanism for determining if a type is derived from another type (see boost library)*/
struct type_traits{
  struct yes_type{int i;};
  struct no_type{};
};
template<bool V>
struct value_t{
};
template<>
struct value_t<true>{
  typedef type_traits::yes_type value;
};
template<>
struct value_t<false>{
  typedef type_traits::no_type value;
};
template<typename B, typename D>
struct derived_from_impl{
  struct Host{
    operator B const volatile *() const;
    operator D const volatile *();
  };
  template<typename T>
  static type_traits::yes_type check_sig(D const volatile *, T);
  static type_traits::no_type  check_sig(B const volatile * const&, int);
  static const bool derived = 
    (sizeof(check_sig(Host(), 0)) == sizeof(type_traits::yes_type));
  typedef value_t<derived> value;
};

Now all you have to do is overload the cast operator for the smart pointer as follows:

template<class T>
class smart_ptr{
public:
  ..
  template<class Y>
  operator smart_ptr<Y>(){
    typename derived_from_impl<Y,T>::value::value temp;
    return wrap_ptr_inernal(temp, (Y*)NULL, *this);
  }
}
//default behavior just casts the type directly...
template<class Y, class T>
strong_shared_ptr<Y> wrap_ptr(type_traits::yes_type, Y *, strong_shared_ptr<T> &ptr){
  return strong_shared_ptr<Y>(ptr);
}
//customizable behavior binds to a wrap_ptr if that has been defined.
template<class Y, class T>
strong_shared_ptr<Y> wrap_ptr(type_traits::no_type, Y *, strong_shared_ptr<T> &ptr){
        return wrap_ptr((Y *)NULL, ptr);
}

So now whenever the compiler encounters a pointer of type A being passed to a method expecting type B where A != B it will first check to see if B is a parent type for A. If so it will just cast the pointer to B. Otherwise it will try to find a wrap_ptr function defined for converting A to B and use that function. If no such function exists the compiler will error out.

As a bonus this will work properly even if there is a wrap function defined for wrapping some type C as B if C is a parent type of A.

The Author

Michael Smit is a software engineer in Seattle, Washington who works for amazon

No comments

No Comments

Leave a comment