30

we want to use pimpl idiom for certain parts of our project. These parts of the project also happen to be parts where dynamic memory allocation is forbidden and this decision is not in our control.

So what i am asking is, is there a clean and nice way of implementing pimpl idiom without dynamic memory allocation?

Edit
Here are some other limitations: Embedded platform, Standard C++98, no external libraries, no templates.

erelender
  • 5,817
  • 29
  • 47
  • Whats the point of pimpl without dynamic allocation? pimpl's primary use is to make the lifetime of dynamic objects managable. If you don't have lifetime management issues, then just pass the reference to the static/stack scoped object around directly. – Chris Becke Feb 07 '11 at 13:55
  • 12
    I think the primary use of pimpl is hiding implementation details, hence the name "pointer to implementation idiom". – erelender Feb 07 '11 at 13:58
  • 3
    @Chris: we don't need pimpl to manage lifetime of objects. Just use a smart pointer (or write the object to follow the RAII idiom in the first place). pimpl is about hiding the internals of a class. – jalf Feb 07 '11 at 14:29
  • 1
    how can someone with 23k rep misunderstand a basic idiom so egregiously – underscore_d Sep 06 '16 at 22:14
  • @underscore_d c++ is a big language, lots of people only use it in an embedded space which means they might never learn about pimpl, because it is largely useless there. – Fantastic Mr Fox Apr 24 '18 at 07:12
  • 2
    @FantasticMrFox It's perfectly fair for someone not to know what it is. But then they shouldn't post false assertions about what it is for. – underscore_d Apr 24 '18 at 09:28

7 Answers7

29

Warning: the code here only showcases the storage aspect, it is a skeleton, no dynamic aspect (construction, copy, move, destruction) has been taken into account.

I would suggest an approach using the C++0x new class aligned_storage, which is precisely meant for having raw storage.

// 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;
};

In the source, you then implement a check:

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
}

This way, while you'll have to change the alignment immediately (if necessary) the size will only change if the object changes too much.

And obviously, since the check is at compilation time, you just cannot miss it :)

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

Matthieu M.
  • 251,718
  • 39
  • 369
  • 642
  • Why is 10% tolerance margin needed here? – Gart Aug 27 '12 at 11:13
  • 3
    @Gart: any change in the size of `Foo` introduces a binary incompatibility, which is what we are trying to prevent here. You thus need *StorageSize* to be superior to `sizeof(Impl)` *and* stable, thus you will probably slightly oversize it so as to be able to add fields to `Impl` later on. However you may overshoot too much and end up with a very large object for... nothing, so I suggest to check that you do not end up with an overly big object either, using this 10% margin. – Matthieu M. Aug 27 '12 at 16:06
  • 2
    I needed to call `new( &_storage )Impl();` in the constructor to get Pimpl members to initialise correctly. – SurvivalMachine Jul 11 '14 at 20:55
  • 1
    I also needed to call `reinterpret_cast< Impl* >( &_storage )->~Impl();` in the destructor to avoid leaking memory. – SurvivalMachine Apr 01 '15 at 19:14
  • @SurvivalMachine: oh my, yes, the code here is very much incomplete and only discusses the storage aspect, no dynamic aspect has been discussed (construction, copy, move, destruction, etc...). I'll add a warning. – Matthieu M. Apr 02 '15 at 06:19
  • The problem with this approach that I see is: how do you know what XXX and YYY are, in a portable way? And by *portable* I also mean portable across multiple versions of the same compiler on the same architecture. – Fabio A. Jul 01 '16 at 13:54
  • @FabioA.: That's the point of possibly having a tolerance margin. Alignment requirements are generally driven by the target CPU and actually vary quite rarely (most often 4 or 8 bytes), it would be fine to simply ensure that the alignment is *higher* than the required one. The size varies more, but as long as you are ready to adjust (or even lift entirely) the upper bound, there is no issue. – Matthieu M. Jul 01 '16 at 14:04
  • I think this is a good solution. I think it can be packaged up in a template like this: https://godbolt.org/z/EcGfW1 – Ben Nov 16 '20 at 14:45
  • @Ben: Nice! I don't think that disabling copy/move is necessary. The implementation should be possible -- though of course it would only be usable where `value_type` is defined. – Matthieu M. Nov 16 '20 at 15:22
  • 1
    To refute Sutter's "Why Attempt #3 is Deplorable" http://www.gotw.ca/gotw/028.htm (which is pre-C++11, I think): 1. I dealt with alignment (and could do better using `std::align` to allow the value to be offset in the buffer) 2. Brittleness: It's now easy to make it statically safe. 3. Maintenance Cost: There are cases where the size won't change but the headers required are expensive. 4. Wasted space: Sometimes I don't care. 5. I'll leave unanswered. My point is I do have a few classes I want as members of vocabulary types but that pull in huge headers. This could fix that; modules may too. – Ben Nov 17 '20 at 13:35
  • 1
    @Ben: Indeed, modules should obsolete the "Compilation Firewall" aspects of PIMPL, and therefore the InlinePimpl... they're still not there yet though, so I think your implementation can serve you well in the mean time :) – Matthieu M. Nov 17 '20 at 13:58
  • 1
    A related class that I've been surprised boost doesn't have is `small_any>` and `static_any` which would be like `boost::container::small_vector` and `...::static_vector` but for any type with small-object optimization and statically-required-small-object-only respectively. (Or likewise with a forward-declared variant. Basically my solution could be considered `static_forward_declared_variant`. – Ben Nov 17 '20 at 15:44
8

pimpl bases on pointers and you can set them to any place where your objects are allocated. This can also be a static table of objects declared in the cpp file. The main point of pimpl is to keep the interfaces stable and hide the implementation (and its used types).

jdehaan
  • 19,108
  • 6
  • 54
  • 94
  • I think that this is the best approach for our case but i don't think that it will be nice and clean like standard pimpl. – erelender Feb 07 '11 at 14:20
  • 2
    IMHO the only downside of this approach is that you have to agree on a maximum number of objects of that type in advance/at compile time. For all other aspects I can think of, the goals of pimpl are reached. – jdehaan Feb 07 '11 at 17:39
  • 4
    Having to decide in advance on the maximum number of objects is not a bug, it is a feature. It is one of the primary rationales behind rules that forbid dynamic memory allocation. Do this and you never run out of memory. And you never have to worry about fragmented heaps. – sbass Feb 07 '11 at 17:59
  • Good point sbass to stress that out, my formulation was a bit negative regarding this aspect. +1 – jdehaan Feb 07 '11 at 21:05
4

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

Gregory Pakosz
  • 65,227
  • 16
  • 134
  • 162
  • 2
    I think writing a fixed allocator misses the whole point of "not using dynamic memory". It may not require dynamic memory allocation but it requires dynamic memory management, which i think is not different than overriding new and delete globally. – erelender Feb 07 '11 at 14:11
4

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 constructed until you deem necessary.

Nim
  • 32,149
  • 2
  • 56
  • 98
  • Sorry, we can't use boost, or any other external library :( – erelender Feb 07 '11 at 14:14
  • 2
    Why are you apologising, you can't help artificial constraints? :) Anyway, if you wanted to, it's pretty straight forward to strip out the code from boost::optional, the cleverest bit of the code is the `aligned_storage` structure which declares a character array taking into account alignment, then it's a simple placement new to construct. – Nim Feb 07 '11 at 14:22
3

One way would be to have a char[] array in your class. Make it large enough for your Impl to fit, and in your constructor, instantiate your Impl in place in your array, with a placement new: new (&array[0]) Impl(...).

You should also ensure you don't have any alignment problems, probably by having your char[] array a member of an union. This:

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

for instance, will make sure the alignment of array[0] will be suitable for an int, double or a pointer.

Thomas
  • 9,398
  • 4
  • 24
  • 35
  • +1: Was writing a longer post, but this is basically it. You could write a second project that gets the size of the impl classes and instruments that into the containing classes, so you don't need to manually track every change. – Binary Worrier Feb 07 '11 at 13:45
  • not sure the members of the union are enough to guarantee alignment – Gregory Pakosz Feb 07 '11 at 13:47
  • 1
    That approach requires us to maintain the size of the char array whenever the implementation changes (and it may change frequently in some places). Also We can't make it big for future because memory is scarce. – erelender Feb 07 '11 at 13:48
  • @erelender: it could be done as a simple preprocessing task though. Compile the file that defines the "inner" class in a small test program which returns its size, and then write that size into the pimpl class definition. Alternatively, a static assert as suggested by @Matthieu M. could be used to alert you if the "predicted size is too small, so the code won't compile unless a valid size is chosen. – jalf Feb 07 '11 at 14:31
  • The `union` trick is not necessary now that `std::aligned_storage` exists (which might use it internally, but ehh, whatever). But a more fundamental problem here is how you said "will be suitable for an int, double or a pointer". For pointers, your example will only be guaranteed to be suitably aligned _for a `char*` pointer_. Remember that pointers to different types are not required to have the same sizes (or representations, or etc.) – underscore_d Sep 06 '16 at 22:18
1

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.

bdonlan
  • 205,037
  • 27
  • 244
  • 316
0

One technique I've used is a non-owning pImpl wrapper. This is a very niche option and isn't as safe as traditional pimpl, but it can help if performance is a concern. It may require some re-architecture to more functional like apis.

You can create a non-owning pimpl class, as long as you can (somewhat) guarantee the stack pimpl object will outlive the wrapper.

For ex.

/* header */
struct MyClassPimpl;
struct MyClass {
    MyClass(MyClassPimpl& stack_object); // Initialize wrapper with stack object.

private:
    MyClassPimpl* mImpl; // You could use a ref too.
};


/* in your implementation code somewhere */

void func(const std::function<void()>& callback) {
    MyClassPimpl p; // Initialize pimpl on stack.

    MyClass obj(p); // Create wrapper.

    callback(obj); // Call user code with MyClass obj.
}

The danger here, like most wrappers, is the user stores the wrapper in a scope that will outlive the stack allocation. Use at your own risk.

scx
  • 1,992
  • 15
  • 23