Pimpl idiom without using dynamic memory allocation

we want to use the pimpl idiom for some parts of our project. These parts of the project are also parts where dynamic memory allocation is prohibited, and this solution is not under our control.

So I ask, is there a clean and nice way to implement the pimpl idiom without dynamically allocating memory?

Edit
Here are some other limitations: embedded platform, standard C ++ 98, external libraries and templates.

+21
c ++ embedded pimpl-idiom dynamic-memory-allocation
Feb 07 2018-11-22T00:
source share
6 answers

Warning: only the storage code is presented here, this is the skeleton, the dynamic aspect (construction, copying, moving, destruction) was taken into account.

I would suggest an approach using the new C ++ 0x aligned_storage , which is designed to store raw data.

 // header class Foo { public: private: struct Impl; Impl& impl() { return reinterpret_cast<Impl&>(_storage); } Impl const& impl() const { return reinterpret_cast<Impl const&>(_storage); } static const size_t StorageSize = XXX; static const size_t StorageAlign = YYY; std::aligned_storage<StorageSize, StorageAlign>::type _storage; }; 

The source code then checks:

 struct Foo::Impl { ... }; Foo::Foo() { // 10% tolerance margin static_assert(sizeof(Impl) <= StorageSize && StorageSize <= sizeof(Impl) * 1.1, "Foo::StorageSize need be changed"); static_assert(StorageAlign == alignof(Impl), "Foo::StorageAlign need be changed"); /// anything } 

Thus, if you need to immediately change the alignment (if necessary), the size will change only if the object has changed too much.

And, obviously, since the check is done at compile time, you just can't miss it :)

If you do not have access to C ++ 0x functions, there are equivalents in the TR1 namespace for aligned_storage and alignof and there are macro implementations of static_assert .

+20
Feb 07 2018-11-11T00:
source share

pimpl bases on pointers, and you can install them at any place where your objects are highlighted. It can also be a static table of objects declared in the cpp file. The main point of pimpl is maintaining the stability of interfaces and hiding the implementation (and the types it uses).

+6
Feb 07 2018-11-11T00:
source share

If you can use boost, consider boost::optional<> . This avoids the cost of dynamic allocation, but at the same time, your object will not be created until you find it necessary.

+4
Feb 07 2018-11-11T00:
source share

One way is to have a char [] array in your class. Make it big enough for your Impl to fit, and in your constructor create an instance of your Impl in your array with a new location: new (&array[0]) Impl(...) .

You should also make sure that you have no alignment problems, perhaps if your char [] array is a member of the union. It:

union { char array[xxx]; int i; double d; char *p; };

for example, make sure the alignment of array[0] is appropriate for an int, double, or pointer.

+3
07 Feb 2018-11-22T00:
source share

See the Fast Pimpl Idiom and The Joy of Pimpls for using a fixed allocator with the pimpl idiom.

+3
Feb 07 2018-11-22T00:
source share

The point of using pimpl is to hide the implementation of your object. This includes the size of the true implementation object. However, it also makes it inconvenient to avoid dynamic allocation - in order to reserve enough stack space for an object, you need to know how large the object is.

A typical solution is to use dynamic allocation and transfer responsibility for allocating sufficient space for (hidden) implementation. However, this is not possible in your case, so we will need another option.

One such parameter uses alloca() . This little-known function allocates memory on the stack; the memory will be automatically freed when the function goes out of scope. This is not portable C ++, however, many C ++ implementations support it (or a variation of this idea).

Note that you must select pimpl'd objects with a macro; alloca() must be called to get the necessary memory directly from its own 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 inconvenient, but it does the analog of pimpl.

If you can hardcode the size of the object in the header, you can also use the 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 clean than the approach described above, since you must change the headers whenever the implementation size changes. However, it allows you to use the usual syntax for initialization.

You can also implement a shadow stack, that is, an extra stack separate from the regular C ++ stack, in particular for storing pImpl'd objects. This requires very careful management, but, properly wrapped, it should work. This type is in the gray zone between dynamic and static distribution.

 // 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 extremely important to make sure that you DO NOT use the shadow stack for objects where the wrapper object is on the heap; this violates the assumption that objects are always destroyed in the reverse order of creation.

+1
Feb 07 2018-11-11T00:
source share



All Articles