简体   繁体   中英

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? Eg 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;
}

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.

class A
{
public:
    void doSomething() {}
};
template <typename T>
class Foo
{
public:
  void bar()
  {
    Impl(m_impl).doSomething();
  }

private:
    template<typename P>
    P& Impl(P* e)
    {
        return *e;
    }
    template<typename P>
    P& Impl(std::shared_ptr<P> e)
    {
        return *e;
    }
    template<typename P>
    P& Impl(P& e)
    {
        return e;
    }
  T m_impl;
};

You can write a caller class template, whose job is to call the function, either using syntax obj.f() or obj->f() , based on the type of obj .

Here is a small example that demonstrates this approach:

template<typename T>
struct caller
{
    static void call(T &obj) {  obj.f(); } //uses obj.f() syntax
};

template<typename T>
struct caller<T*>
{
    static void call(T* obj) {  obj->f(); } //uses obj->f() syntax
};

And this caller class template is used by this sample class:

template<typename T>
struct X
{
   T obj;
   X(T o) : obj(o) {}
   void h()
   {
        caller<T>::call(obj); //this selects the appropriate syntax!
   }
};

See this online running demo at ideone : http://www.ideone.com/H18n7

--

EDIT:

This is even more generic. Here you can even pass the function which you want to call in caller . Now caller is not hard-coded with the function to be called!

http://www.ideone.com/83H52

You can introduce another intermediate template class, something like that:

template < typename T >
class FooBase
{
private:
    T m_impl;

protected:
    T& impl() { return m_impl; }
};

template < typename T >
class FooBase< boost::shared_ptr< T > >
{
private:
    boost::shared_ptr< T > m_impl;

protected:
    T& impl() { return *(m_impl.operator ->()); }
};

template < typename T >
class Foo : protected FooBase< T >
{
public:
    void bar()
    {
        impl().DoSomething();
    }
};

Now, you only have to code the Foo class only once. And you can specialize it for other smart pointers type by doing partial specialization on FooBase .

Edit: You can also use composition instead of having an inheritance relationship between Foo and FooBase (in which case, I'd probably rename it to FooHelper or something like that).

template < typename T >
class FooHelper
{
private:
    T m_impl;

public:
    T& impl() { return m_impl; }
};

template < typename T >
class FooHelper< boost::shared_ptr< T > >
{
private:
    boost::shared_ptr< T > m_impl;

public:
    T& impl() { return *(m_impl.operator ->()); }
};

template < typename T >
class Foo
{
private:
    FooHelper< T > m_helper;

public:
    void bar()
    {
        m_helper.impl().DoSomething();
    }
};

I really question whether you should be using a template here at all. Your template parameter has a very clear interface and therefore looks like you should just use an abstract base class.

Do you really need to have an instance? If you do need to change the way the object is represented, this should be done as a separate exercise and not part of the template that uses it.

You can use partial specialization.

template <typename T>
class Foo
{
public:
  //...
};

template<typename T> class Foo<boost::shared_ptr<T>> {
  //... implement specialization here
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM