bremen_matt
bremen_matt

Reputation: 7349

C++ Pimpl Idiom using a pre-existing class

We have a heavily-templated header-only codebase that a client would like access to. For example, let's say it contains the Foo class in the header foo.hpp:

#ifndef FOO_HEADER
#define FOO_HEADER

#include <iostream>

template <typename T>
struct Foo {

    Foo(){
       // Do a bunch of expensive initialization
    }

    void bar(T t){
        std::cout << t; 
    }

    // Members to initialize go here... 
};

#endif /* FOO_HEADER */

Now we want to let the client try a reduced set of the functionality without exposing the core code and without rewriting the whole codebase.

One idea would be to use the PIMPL idiom to wrap this core code. Specifically, we could create a FooWrapper class with the header foo_wrapper.hpp:

#ifndef FOO_WRAPPER_HEADER
#define FOO_WRAPPER_HEADER

#include <memory>

struct FooWrapper {

    FooWrapper();
    ~FooWrapper();

    void bar(double t);

    private: 
        struct Impl;
        std::unique_ptr<Impl> impl;
};

#endif /* FOO_WRAPPER_HEADER */

and implementation foo_wrapper.cpp:

#include "foo.hpp"
#include "foo_wrapper.hpp"

struct FooWrapper::Impl {
    Foo<double> genie;
};

void FooWrapper::bar(double t){
    impl->genie.bar(t);
}

FooWrapper::FooWrapper() : impl(new Impl){
}

FooWrapper::~FooWrapper() = default;

This code works as I expect it: https://wandbox.org/permlink/gso7mbe0UEOOPG7j

However, there is one little nagging thing that is bothering me. Specifically, the implementation requires what feels like an additional level of indirection... We have to define the Impl class to hold a member of the Foo class. Because of this, all of the operations have this indirection of the form impl->genie.bar(t);.

It would be better if we could somehow tell the compiler, "Actually Impl IS the class Foo<double>", in which case, we could instead say impl->bar(t);.

Specifically, I am thinking something along the lines of typedef or using to get this to work. Something like

using FooWrapper::Impl = Foo<double>;

But this does not compile. So on to the questions:

  1. Is there a nice way to get rid of this indirection?
  2. Is there a better idiom I should be using?

I am targeting a C++11 solution, but C++14 may work as well. The important thing to remember is that the solution can't use the header foo.hpp in foo_wrapper.hpp. Somehow we have to compile that code into a library and distribute just the compiled library and the foo_wrapper header.

Upvotes: 3

Views: 343

Answers (2)

bolov
bolov

Reputation: 75698

Just use Foo<double>:

// forward declaration so that you don't need to include "Foo.hpp"
template class Foo<double>;

struct FooWrapper {
    //...
    std::unique_ptr<Foo<double>> impl;
};

// explicit template instantiation so that Foo<double> exists without distributing "Foo.hpp"
template class Foo<double>;

void FooWrapper::bar(double t){
    impl->bar(t);
}

Upvotes: 1

Mike van Dyke
Mike van Dyke

Reputation: 2868

You can just forward-declare Foo in FooWrapper.h. This will allow you to declare a std::unique_ptr for it:

#ifndef FOO_WRAPPER_HEADER
#define FOO_WRAPPER_HEADER

#include <memory>

// Forward declaration
template <typename T>
class Foo;

struct FooWrapper {
  FooWrapper();
  ~FooWrapper();

  void bar(double t);

 private:
  std::unique_ptr<Foo<double>> impl;
};

#endif /* FOO_WRAPPER_HEADER */

foo_wrapper.cc:

#include "foo_wrapper.h"
#include "foo.h"

void FooWrapper::bar(double t) {
  impl->bar(t);
}

FooWrapper::FooWrapper() : impl(std::make_unique<Foo<double>>()) {}

FooWrapper::~FooWrapper() = default;

Upvotes: 4

Related Questions