简体   繁体   中英

Is there any way to limit repetitive boilerplate when using the PIMPL idiom?

I have something like the following:

// foo.h:
class foo {
public:
    foo(); 
    ~foo();
    // note: the param type repetition here is only incidental, assume the
    // functions can't easily be made to share type signatures
    void bar(a b, c d);               
    void baz(a b, c d, e f, g h, i j);
    void quux(a b, c d, e f, g h, i j);
private:
    class impl;
    impl *m_pimpl;
}

Then:

// foo.cpp:
class foo::impl {
public:
    void bar(a b, c d);
    void baz(a b, c d, e f, g h, i j);
    void quux(a b, c d, e f, g h, i j);

private:
    // lots of private state, helper functions, etc. 
};

void foo::impl::bar(a b, c d) { ... }
void foo::impl::baz(a b, c d, e f, g h, i j) { ... }
void foo::impl::quux(a b, c d, e f, g h, i j) { ... }

foo::foo() : m_pimpl(new impl()) { }
foo::~foo() { delete m_pimpl; m_pimpl = NULL; }
void foo::bar(a b, c d) {
    return m_pimpl->bar(b, d);
}
void foo::baz(a b, c d, e f, g h, i j) {
    return m_pimpl->baz(b, d, f, h, j)
}
void foo::quux(a b, c d, e f, g h, i j) {
    return m_pimpl->quux(b, d, f, h, j);
}

There's a lot of repetition of bar , baz , and quux here:

  • Once in foo 's declaration
  • Once in foo::impl 's declaration
  • Once in foo::impl 's definitions
  • Twice in foo 's definitions: once for foo 's function param list and again to call the corresponding foo::impl functions.

For each of these except the last I have to write out the entire parameter list, whose types can be more involved.

What's the best way, if any, to reduce the repetition here? One easy one is to inline the definition of the foo::impl public functions, but is there anything besides that outside of designing the classes differently to have fewer public functions?

Using signatures, we can reduce it to 3 mentions, plus 1 set of forwards:

using signature_1 = int(int);

struct foo {
  signature_1 x;
  foo();
  ~foo();
private:
  struct fooimpl;
  std::unique_ptr<fooimpl> pimpl;
};

int main() {
  foo f;
  std::cout << f.x(1) << '\n';
}
struct foo::fooimpl {
  signature_1 x;
  int v = 3;
};

foo::~foo() = default;
foo::foo():pimpl(new foo::fooimpl()){}

int foo::x(int y){return pimpl->x(y);}
int foo::fooimpl::x(int y){return y+v;}

We can get it down to 2+forward if our pimpl is a pure virtual class. Write a map from decltype(&foo::method) ->signature, and have the pure virtual interface use that (and decltype) to invent the signature of the pure virtual class.

Write the signature once in foo , decltype-determine it in foo_impl_interface , and then implement it inline within foo_impl in the cpp file. Plus one set of forwards.

 template<class MethodPtrType>
 struct method_sig;
 template<class MethodPtrType>
 using method_sig_t = typename method_sig<MethodPtrType>::type;
 template<class T, class R, class...Args>
 struct method_sig< R(T::*)(Args...) > {
   using type=R(Args...);
 };

plus another 11 odd specializations (sigh) to get all the cases. ( const& const const&& const volatile const volatile& const volatile&& & && volatile volatile& volatile&& qualifiers are all, as far as I can tell, required).

Now method_sig_t<decltype(&foo::x)> is int(int) , which we can use:

struct foo_impl_interface {
  virtual method_sig_t<decltype(&foo::x)> x = 0;
  virtual ~foo_impl_interface() {}
};

assuming we are using pimpl to regularize our type rather than hide the state.


Finally, instead of implementing most of your code in the pimpl, instead just store STATE in the pimpl, leave the code in the class itself.

This gives you the "I others do not depend on my size": who cares if the code is in foo or foo_impl that reads foo_impl 's state? So if you aren't doing the foo_impl_interface technique, why forward?

If your public class just proxies calls to the implementation, consider using interface -> implementation model instead of pimpl.

You would reference your implementation only by the interface in the user code with class foo being interface

class foo {
public:
    virtual void bar(a b, c d) = 0;               
    virtual void baz(a b, c d, e f, g h, i j) = 0;
    virtual void quux(a b, c d, e f, g h, i j) = 0;
    virtual ~foo(){}
}

Idea behind pimpl is to store private data and functions in the separate object pointer so you don't break your public class interface in case of data storage and handling change. But it does not imply all code moved to the private object. So you usually implement your public functions just in place. You wouldn't break user code when your public functions implementation change, as your public functions implementation will be hiden from the user along with private class definition.

One way you can do this is by defining an interface class so you don't need to redeclare everything and use pass-through pointer semantics (overloaded operator->)

Here is an example: Is it possible to write an agile Pimpl in c++?

To close out this question: ultimately I think Adrian's comment addresses the issue the best: "I tend to implement the impl class's methods in the class definition, which reduces some of the repetition."

My code above would become:

// foo.cpp:
class foo::impl {
public:
    // lots of private state, helper functions, etc. 
};

foo::foo() : m_pimpl(new impl()) { }
foo::~foo() { delete m_pimpl; m_pimpl = NULL; }
void foo::bar(a b, c d) {
    ... code using m_pimpl-> when necessary ...
}
void foo::baz(a b, c d, e f, g h, i j) {
    ... code using m_pimpl-> when necessary ...
}
void foo::quux(a b, c d, e f, g h, i j) {
    ... code using m_pimpl-> when necessary ...
}

Now it's much more reasonable - just one for the declaration and one for the definition. There's the small overhead when converting a class to use pimpl of adding the m_pimpl-> s, but IMO this is less annoying than having all the repetition.

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