Search code examples
c++templatesrefactoringshared-ptr

Refactoring c++ template class based on template type


Given class Foo

template <typename T>
class Foo
{
public:

  ...other methods..

  void bar()
  {
    ...
    m_impl.doSomething();
    ...
  }

  void fun()
  {
    ...
    m_impl.doSomethingElse();
    ...
  }

  void fubar()
  {
    ...
  }

private:
  T m_impl;
};

I wanted to cater for situations where T is a boost::shared_ptr. In this case the only change to class Foo is that it should invoke

m_impl->doSomething();

instead of

m_impl.doSomething();

I ended up defining FooPtr in the same header

template <typename T>
class FooPtr
{
public:
  ...other methods..

  void bar()
  {
    ...
    m_pImpl->doSomething();
    ...
  }

  void fun()
  {
    ...
    m_pImpl->doSomethingElse();
    ...
  }

  void fubar()
  {
    ...
  }

private:
  boost::shared_ptr<T> m_pImpl;
};

Now while the approach works for all classes that I want to use with Foo, the problem is that I have a lot of duplicate code lying around and any changes I make to Foo, I also have to make to FooPtr.

How can I refactor the code? E.g. Is there any way that I can determine at compile time if T is of type boost::shared_ptr, and then specialise just the bar and fun methods to invoke the -> operator?

Edit: Thanks for all the answers so far! I just need some time to work through them all and see which solution is the best fit for our software.

Edit 2: @Matthieu: This is the test code I was using

class FooImpl
{
public:
  void doIt()
  {
    cout << "A" << std::endl;
  }
};

int _tmain(int argc, _TCHAR* argv[])
{
  Foo<FooImpl> foo;
  foo.doSomething();
  return 0;
}

Solution

  • Sylvain wrote a DRY solution, but I don't like abusing inheritance.

    Using a wrapper class to uniformize the interface is easy, especially since pointer semantics work so well!

    namespace details {
      template <typename T>
      struct FooDeducer {
        typedef boost::optional<T> type;
      };
    
      template <typename T>
      struct FooDeducer< T* > {
        typedef T* type;
      };
    
      template <typename T>
      struct FooDeducer< boost::shared_ptr<T> > {
        typedef boost::shared_ptr<T> type;
      };
    } // namespace details
    
    template <typename T>
    class Foo {
    public:
      // methods
      void doSomething() { impl->doIt(); }
    
    private:
      typedef typename details::FooDeducer<T>::type Type;
      Type impl;
    };
    

    Here, relying on boost::optional which provides the OptionalPointee semantics, we nearly get the same behavior than pointers.

    One point I'd like to emphasize though, is the difference in the copying behavior. boost::optional provides deep copy.