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:
foo
's declarationfoo::impl
's declarationfoo::impl
's definitionsfoo
'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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With