The point of using pimpl is to hide the implementation of your object. This includes the size of the true implementation object. However this also makes it awkward to avoid dynamic allocation - in order to reserve sufficient stack space for the object, you need to know how big the object is.
The typical solution is indeed to use dynamic allocation, and pass the responsibility for allocating sufficient space to the (hidden) implementation. However, this isn't possible in your case, so we'll need another option.
One such option is using alloca()
. This little-known function allocates memory on the stack; the memory will be automatically freed when the function exits its scope. This is not portable C++, however many C++ implementations support it (or a variation on this idea).
Note that you must allocate your pimpl'd objects using a macro; alloca()
must be invoked to obtain the necessary memory directly from the owning function. Example:
// Foo.h
class Foo {
void *pImpl;
public:
void bar();
static const size_t implsz_;
Foo(void *);
~Foo();
};
#define DECLARE_FOO(name) \
Foo name(alloca(Foo::implsz_));
// Foo.cpp
class FooImpl {
void bar() {
std::cout << "Bar!\n";
}
};
Foo::Foo(void *pImpl) {
this->pImpl = pImpl;
new(this->pImpl) FooImpl;
}
Foo::~Foo() {
((FooImpl*)pImpl)->~FooImpl();
}
void Foo::Bar() {
((FooImpl*)pImpl)->Bar();
}
// Baz.cpp
void callFoo() {
DECLARE_FOO(x);
x.bar();
}
This, as you can see, makes the syntax rather awkward, but it does accomplish a pimpl analogue.
If you can hardcode the size of the object in the header, there's also the option of using a char array:
class Foo {
private:
enum { IMPL_SIZE = 123; };
union {
char implbuf[IMPL_SIZE];
double aligndummy; // make this the type with strictest alignment on your platform
} impl;
// ...
}
This is less pure than the above approach, as you must change the headers whenever the implementation size changes. However, it allows you to use normal syntax for initialization.
You could also implement a shadow stack - that is, a secondary stack separate from the normal C++ stack, specifically to hold pImpl'd objects. This requires very careful management, but, properly wrapped, it should work. This sort of is in the grey zone between dynamic and static allocation.
// One instance per thread; TLS is left as an exercise for the reader
class ShadowStack {
char stack[4096];
ssize_t ptr;
public:
ShadowStack() {
ptr = sizeof(stack);
}
~ShadowStack() {
assert(ptr == sizeof(stack));
}
void *alloc(size_t sz) {
if (sz % 8) // replace 8 with max alignment for your platform
sz += 8 - (sz % 8);
if (ptr < sz) return NULL;
ptr -= sz;
return &stack[ptr];
}
void free(void *p, size_t sz) {
assert(p == stack[ptr]);
ptr += sz;
assert(ptr < sizeof(stack));
}
};
ShadowStack theStack;
Foo::Foo(ShadowStack *ss = NULL) {
this->ss = ss;
if (ss)
pImpl = ss->alloc(sizeof(FooImpl));
else
pImpl = new FooImpl();
}
Foo::~Foo() {
if (ss)
ss->free(pImpl, sizeof(FooImpl));
else
delete ss;
}
void callFoo() {
Foo x(&theStack);
x.Foo();
}
With this approach it is critical to ensure that you do NOT use the shadow stack for objects where the wrapper object is on the heap; this would violate the assumption that objects are always destroyed in reverse order of creation.