Pimpl idiom without dynamic memory allocation

Discussion in 'C++' started by Daniel Lidström, Oct 17, 2007.

  1. Hello!

    I have just discovered a way to use the private implementation idiom
    (pimpl), without the overhead of dynamic memory allocation. For those of
    you who don't know what this is, Wikipedia has a nice article you can
    read. Anyway, I discovered that if you make all members in the
    implementation class mutable, you can in fact use this idiom without any
    "unnecessary" memory allocation. Here's a minimal example of the method:

    // In the header of your class called Line

    #include <string>

    class Line
    {
    public:

    Line(const std::string& name);
    const std::string& GetName() const;
    void SetName(const std::string& s);

    private:

    // Private implementation idiom:
    // all member variables are hidden in this class
    class LineImpl;
    const LineImpl& m_pimpl; // normally a non-const pointer
    };

    // and in your implementation file:

    #include "Line.h"

    // Here we define the class with the member variables
    class Line::LineImpl
    {
    public:

    LineImpl(const std::string& s) : m_s(s) {}
    // all methods need to be const here
    const std::string& GetName() const { return m_s; }
    void SetName(const std::string& s) const { m_s = s; }

    private:

    mutable std::string m_s; // the trick! all members are mutable
    };

    // create the pimpl instance without using new
    Line::Line(const std::string& s) : m_pimpl(LineImpl(s)) {}

    // forward all member functions to the private implementation
    const std::string& Line::GetName() const
    {
    return m_pimpl.GetName();
    }

    void Line::SetName(const std::string& s)
    {
    m_pimpl.SetName(s);
    }

    Ok experts, what do you all think? This method sacrifies
    const-correctness for some extra speed. Is it worth it?

    --
    Daniel
     
    Daniel Lidström, Oct 17, 2007
    #1
    1. Advertising

  2. Daniel Lidström a écrit :
    > Hello!
    >
    > I have just discovered a way to use the private implementation idiom
    > (pimpl), without the overhead of dynamic memory allocation. For those of
    > you who don't know what this is, Wikipedia has a nice article you can
    > read. Anyway, I discovered that if you make all members in the
    > implementation class mutable, you can in fact use this idiom without any
    > "unnecessary" memory allocation. Here's a minimal example of the method:
    >
    > // In the header of your class called Line
    >
    > #include <string>
    >
    > class Line
    > {
    > public:
    >
    > Line(const std::string& name);
    > const std::string& GetName() const;
    > void SetName(const std::string& s);
    >
    > private:
    >
    > // Private implementation idiom:
    > // all member variables are hidden in this class
    > class LineImpl;
    > const LineImpl& m_pimpl; // normally a non-const pointer
    > };
    >
    > // and in your implementation file:
    >
    > #include "Line.h"
    >
    > // Here we define the class with the member variables

    [snip]
    > // all methods need to be const here

    [snip]
    > mutable std::string m_s; // the trick! all members are mutable


    Which mean you coerce the code into compilation. That's all.

    > // create the pimpl instance without using new
    > Line::Line(const std::string& s) : m_pimpl(LineImpl(s)) {}


    Your local is destroyed when going out of scope. Doesn't it ?

    > [snip]
    > Ok experts, what do you all think? This method sacrifies
    > const-correctness for some extra speed. Is it worth it?


    Not really.
    And certainly not worth a dangling reference.


    Michael
     
    Michael DOUBEZ, Oct 17, 2007
    #2
    1. Advertising

  3. In article <4716585e$0$25087$>,
    Michael DOUBEZ <> wrote:

    > Daniel Lidström a écrit :
    > > // create the pimpl instance without using new
    > > Line::Line(const std::string& s) : m_pimpl(LineImpl(s)) {}

    >
    > Your local is destroyed when going out of scope. Doesn't it ?


    No it isn't. It is actually ok to bind a temporary object to a const
    reference. There will be no "dangling" reference.

    --
    Daniel
     
    Daniel Lidström, Oct 17, 2007
    #3
  4. Daniel Lidström a écrit :
    > In article <4716585e$0$25087$>,
    > Michael DOUBEZ <> wrote:
    >
    >> Daniel Lidström a écrit :
    >>> // create the pimpl instance without using new
    >>> Line::Line(const std::string& s) : m_pimpl(LineImpl(s)) {}

    >> Your local is destroyed when going out of scope. Doesn't it ?

    >
    > No it isn't. It is actually ok to bind a temporary object to a const
    > reference. There will be no "dangling" reference.


    It is ok to bind it but that doesn't mean the lifetime of the temporary
    is extended.

    Example:
    const std::string& foo()
    {
    return std::string("bar");
    }

    The value returned by foo() is an dangling reference.

    Michael
     
    Michael DOUBEZ, Oct 17, 2007
    #4
  5. Daniel Lidström

    Daniel T. Guest

    Daniel Lidström <> wrote:

    > // In the header of your class called Line
    >
    > #include <string>
    >
    > class Line
    > {
    > public:
    >
    > Line(const std::string& name);
    > const std::string& GetName() const;
    > void SetName(const std::string& s);
    >
    > private:
    >
    > // Private implementation idiom:
    > // all member variables are hidden in this class
    > class LineImpl;
    > const LineImpl& m_pimpl; // normally a non-const pointer
    > };
    >
    > // and in your implementation file:
    >
    > #include "Line.h"
    >
    > // Here we define the class with the member variables
    > class Line::LineImpl
    > {
    > public:
    >
    > LineImpl(const std::string& s) : m_s(s) {}
    > // all methods need to be const here
    > const std::string& GetName() const { return m_s; }
    > void SetName(const std::string& s) const { m_s = s; }
    >
    > private:
    >
    > mutable std::string m_s; // the trick! all members are mutable
    > };
    >
    > // create the pimpl instance without using new
    > Line::Line(const std::string& s) : m_pimpl(LineImpl(s)) {}


    Where would the memory for the LineImpl object be placed? It isn't
    embedded in the object, nor is it in the heap, and it can't be placed on
    the stack (and still survive the call to the c_tor.)

    Doesn't sound like a good idea to me.
     
    Daniel T., Oct 17, 2007
    #5
  6. Daniel Lidström wrote:
    > I have just discovered a way to use the private implementation idiom
    > (pimpl), without the overhead of dynamic memory allocation.

    [ storing a reference to a temporary ]

    As you noticed, this doesn't work. However, there is a method that works.
    All you have to do is to add a suitably aligned and sufficiently large
    buffer into the class:

    class foo {
    aligned_storage<42> m_impl;
    class implementation;
    foo();
    ~foo();
    void some_function();
    };

    class foo::implementation { ... };

    foo::foo() {
    // placement new
    new m_impl.get<void>() implementation;
    }
    foo::~foo() {
    // explicit dtor invokation
    m_impl.get<implementation>()->~implementation;
    }
    void foo::some_function() {
    m_impl.get<implementation>()->some_function();
    }

    Is it worth the hassle? Typically not, in particular since it's hard to
    guarantee that you have both enough but still not too much memory.

    Uli
     
    Ulrich Eckhardt, Oct 18, 2007
    #6
  7. Daniel Lidström

    James Kanze Guest

    On Oct 17, 10:36 pm, "Daniel T." <> wrote:
    > Daniel Lidström <> wrote:
    > > // In the header of your class called Line


    > > #include <string>


    > > class Line
    > > {
    > > public:
    > > Line(const std::string& name);
    > > const std::string& GetName() const;
    > > void SetName(const std::string& s);


    > > private:
    > > // Private implementation idiom:
    > > // all member variables are hidden in this class
    > > class LineImpl;
    > > const LineImpl& m_pimpl; // normally a non-const pointer
    > > };


    > > // and in your implementation file:


    > > #include "Line.h"


    > > // Here we define the class with the member variables
    > > class Line::LineImpl
    > > {
    > > public:
    > > LineImpl(const std::string& s) : m_s(s) {}
    > > // all methods need to be const here
    > > const std::string& GetName() const { return m_s; }
    > > void SetName(const std::string& s) const { m_s = s; }
    > > private:
    > > mutable std::string m_s; // the trick! all members are mutable
    > > };


    > > // create the pimpl instance without using new
    > > Line::Line(const std::string& s) : m_pimpl(LineImpl(s)) {}


    > Where would the memory for the LineImpl object be placed? It
    > isn't embedded in the object, nor is it in the heap, and it
    > can't be placed on the stack (and still survive the call to
    > the c_tor.)


    >From the standard (§12.2/5): "A temporary bound to a reference

    member in a constructor's ctor-initializer persists until the
    constructor exits." In colloquial terms: the temporary is
    created on the stack, and destructed before returning from the
    constructor.

    > Doesn't sound like a good idea to me.


    It isn't, unless you like undefined behavior and hard to find
    bugs.

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
     
    James Kanze, Oct 18, 2007
    #7
  8. In article <47165e09$0$26728$>,
    Michael DOUBEZ <> wrote:
    >Daniel Lidström a écrit :
    >> In article <4716585e$0$25087$>,
    >> Michael DOUBEZ <> wrote:
    >>
    >>> Daniel Lidström a écrit :
    >>>> // create the pimpl instance without using new
    >>>> Line::Line(const std::string& s) : m_pimpl(LineImpl(s)) {}
    >>> Your local is destroyed when going out of scope. Doesn't it ?

    >>
    >> No it isn't. It is actually ok to bind a temporary object to a const
    >> reference. There will be no "dangling" reference.

    >
    >It is ok to bind it but that doesn't mean the lifetime of the temporary
    >is extended.
    >
    >Example:
    >const std::string& foo()
    >{
    > return std::string("bar");
    >}
    >
    >The value returned by foo() is an dangling reference.
    >


    Euh, it's not what he is doing, it's more like:

    std::string foo()
    {
    return std::string("bar");
    }

    int main()
    {
    std::string const & val = foo();
    ....
     
    Yannick Tremblay, Oct 18, 2007
    #8
  9. Daniel Lidström

    Kai-Uwe Bux Guest

    Yannick Tremblay wrote:

    > In article <47165e09$0$26728$>,
    > Michael DOUBEZ <> wrote:
    >>Daniel Lidström a écrit :
    >>> In article <4716585e$0$25087$>,
    >>> Michael DOUBEZ <> wrote:
    >>>
    >>>> Daniel Lidström a écrit :
    >>>>> // create the pimpl instance without using new
    >>>>> Line::Line(const std::string& s) : m_pimpl(LineImpl(s)) {}
    >>>> Your local is destroyed when going out of scope. Doesn't it ?
    >>>
    >>> No it isn't. It is actually ok to bind a temporary object to a const
    >>> reference. There will be no "dangling" reference.

    >>
    >>It is ok to bind it but that doesn't mean the lifetime of the temporary
    >>is extended.
    >>
    >>Example:
    >>const std::string& foo()
    >>{
    >> return std::string("bar");
    >>}
    >>
    >>The value returned by foo() is an dangling reference.
    >>

    >
    > Euh, it's not what he is doing, it's more like:
    >
    > std::string foo()
    > {
    > return std::string("bar");
    > }
    >
    > int main()
    > {
    > std::string const & val = foo();
    > ...


    It's neither. What he is doing is initialzing a reference member from a
    temporary object. The lifetime of that object lasts exactly to the end of
    the constructor call. Afterwards, i.e., for the entire lifetime of the
    fully constructed object, the reference is dangling. From the standard:

    [...] A temporary bound to a reference member in a constructor?s
    ctor-initializer (12.6.2) persists until the constructor exits. ...
    [12.2/5]

    This provision makes you wonder. What is the point of restricting the
    life-time of the temporary to the duration of the constructor if the object
    thus initialized is bound to have a dangling reference ever after?


    Best

    Kai-Uwe Bux
     
    Kai-Uwe Bux, Oct 18, 2007
    #9
  10. Daniel Lidström

    Joe Greer Guest

    Kai-Uwe Bux <> wrote in
    news:ff7r0f$rg6$:

    > It's neither. What he is doing is initialzing a reference member from
    > a temporary object. The lifetime of that object lasts exactly to the
    > end of the constructor call. Afterwards, i.e., for the entire lifetime
    > of the fully constructed object, the reference is dangling. From the
    > standard:
    >
    > [...] A temporary bound to a reference member in a constructor?s
    > ctor-initializer (12.6.2) persists until the constructor exits. ...
    > [12.2/5]
    >
    > This provision makes you wonder. What is the point of restricting the
    > life-time of the temporary to the duration of the constructor if the
    > object thus initialized is bound to have a dangling reference ever
    > after?
    >


    It is interesting. I would have thought that it would last until the scope
    in which the constructor was invoked exited and the stack space is
    reclaimed. There is probably some case for creating the temporaries within
    the scope of the constructor or something that I don't see at the moment.
    In any case, I can't see this for the pimpl idiom.

    joe
     
    Joe Greer, Oct 18, 2007
    #10
  11. Daniel Lidström

    Andre Kostur Guest

    Kai-Uwe Bux <> wrote in
    news:ff7r0f$rg6$:

    > Yannick Tremblay wrote:
    >
    >> In article <47165e09$0$26728$>,
    >> Michael DOUBEZ <> wrote:
    >>>Daniel Lidström a écrit :
    >>>> In article <4716585e$0$25087$>,
    >>>> Michael DOUBEZ <> wrote:
    >>>>
    >>>>> Daniel Lidström a écrit :
    >>>>>> // create the pimpl instance without using new
    >>>>>> Line::Line(const std::string& s) : m_pimpl(LineImpl(s)) {}
    >>>>> Your local is destroyed when going out of scope. Doesn't it ?
    >>>>
    >>>> No it isn't. It is actually ok to bind a temporary object to a
    >>>> const reference. There will be no "dangling" reference.
    >>>
    >>>It is ok to bind it but that doesn't mean the lifetime of the
    >>>temporary is extended.
    >>>
    >>>Example:
    >>>const std::string& foo()
    >>>{
    >>> return std::string("bar");
    >>>}
    >>>
    >>>The value returned by foo() is an dangling reference.
    >>>

    >>
    >> Euh, it's not what he is doing, it's more like:
    >>
    >> std::string foo()
    >> {
    >> return std::string("bar");
    >> }
    >>
    >> int main()
    >> {
    >> std::string const & val = foo();
    >> ...

    >
    > It's neither. What he is doing is initialzing a reference member from
    > a temporary object. The lifetime of that object lasts exactly to the
    > end of the constructor call. Afterwards, i.e., for the entire lifetime
    > of the fully constructed object, the reference is dangling. From the
    > standard:
    >
    > [...] A temporary bound to a reference member in a constructor?s
    > ctor-initializer (12.6.2) persists until the constructor exits. ...
    > [12.2/5]
    >
    > This provision makes you wonder. What is the point of restricting the
    > life-time of the temporary to the duration of the constructor if the
    > object thus initialized is bound to have a dangling reference ever
    > after?


    I don't have the Standard handy... but does it change the details since
    it's a const-reference?
     
    Andre Kostur, Oct 18, 2007
    #11
  12. Daniel Lidström

    Joe Greer Guest

    Andre Kostur <> wrote in news:Xns99CD599F98AC5nntpspamkosutrnet@
    209.135.99.21:

    >
    > I don't have the Standard handy... but does it change the details since
    > it's a const-reference?
    >


    As far as I can tell from the standard, it doesn't. In my own personal mind's eye of how things
    work, I always imagine temporaries allocated on the stack and as soon as the appropriate stack
    scope is gone, so is the temporary. I don't think that temporaries have to be on the stack, but
    the implementations I have looked at act as though they are.

    joe
     
    Joe Greer, Oct 18, 2007
    #12
  13. Daniel Lidström

    James Kanze Guest

    On Oct 18, 4:35 pm, Kai-Uwe Bux <> wrote:
    > Yannick Tremblay wrote:


    > It's neither. What he is doing is initialzing a reference
    > member from a temporary object. The lifetime of that object
    > lasts exactly to the end of the constructor call. Afterwards,
    > i.e., for the entire lifetime of the fully constructed object,
    > the reference is dangling. From the standard:


    > [...] A temporary bound to a reference member in a constructor?s
    > ctor-initializer (12.6.2) persists until the constructor exits. ...
    > [12.2/5]


    > This provision makes you wonder. What is the point of
    > restricting the life-time of the temporary to the duration of
    > the constructor if the object thus initialized is bound to
    > have a dangling reference ever after?


    Implementation constraints: where would the compiler put the
    temporary if it were required to outlast the constructor? (I
    suppose that the standard could have made it illegal to
    initialize a reference member with a temporary.)

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
     
    James Kanze, Oct 19, 2007
    #13
  14. Daniel Lidström

    James Kanze Guest

    On Oct 18, 5:45 pm, Andre Kostur <> wrote:
    > Kai-Uwe Bux <> wrote
    > innews:ff7r0f$rg6$:
    > > It's neither. What he is doing is initialzing a reference member from
    > > a temporary object. The lifetime of that object lasts exactly to the
    > > end of the constructor call. Afterwards, i.e., for the entire lifetime
    > > of the fully constructed object, the reference is dangling. From the
    > > standard:


    > > [...] A temporary bound to a reference member in a constructor?s
    > > ctor-initializer (12.6.2) persists until the constructor exits. ...
    > > [12.2/5]


    > > This provision makes you wonder. What is the point of restricting the
    > > life-time of the temporary to the duration of the constructor if the
    > > object thus initialized is bound to have a dangling reference ever
    > > after?


    > I don't have the Standard handy... but does it change the details since
    > it's a const-reference?


    Yes. If the reference isn't to const, it can't be initialized
    with a temporary at all, so the lifetime of such a temporary
    doesn't have any meaning.

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
     
    James Kanze, Oct 19, 2007
    #14
  15. Daniel Lidström

    James Kanze Guest

    On Oct 18, 5:08 pm, Joe Greer <> wrote:
    > Kai-Uwe Bux <> wrote
    > innews:ff7r0f$rg6$:


    > > It's neither. What he is doing is initialzing a reference member from
    > > a temporary object. The lifetime of that object lasts exactly to the
    > > end of the constructor call. Afterwards, i.e., for the entire lifetime
    > > of the fully constructed object, the reference is dangling. From the
    > > standard:


    > > [...] A temporary bound to a reference member in a constructor?s
    > > ctor-initializer (12.6.2) persists until the constructor exits. ...
    > > [12.2/5]


    > > This provision makes you wonder. What is the point of restricting the
    > > life-time of the temporary to the duration of the constructor if the
    > > object thus initialized is bound to have a dangling reference ever
    > > after?


    > It is interesting. I would have thought that it would last until the scope
    > in which the constructor was invoked exited and the stack space is
    > reclaimed. There is probably some case for creating the temporaries within
    > the scope of the constructor or something that I don't see at the moment.
    > In any case, I can't see this for the pimpl idiom.


    The temporary is created in the constructor, not in the code
    which calls the constructor. The code which calls the
    constructor doesn't even know that the temporary might exist.

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
     
    James Kanze, Oct 19, 2007
    #15
  16. I noticed above that there was a suggestion using:
    aligned_storage<..>. From my understanding this class does not exit in
    the current C++ standard but is being considered for C++0x ? Also is
    there any runtime overhead caused by the overloaded operator -> in
    that implementation. If not then it it probably a much better
    implementation than what i started working on last night...

    Following on from this thread, i have been working on an
    implementation of the PIMPL pattern that i think will work by
    allocating correctly on the stack. Would others be able to take a look
    at it and let me know if this will fail? I have tested it with GCC on
    linux + MinGW and MSVC. The point of this is to try and remove the
    small runtime penalty from the pointer dereference with the typical
    PImpl implementation.

    I have created a number of macros that will let me achieve this simply
    using a single class definition which using macro trickery changes the
    internals of the class between the header presented to the public and
    the cpp file that implements the internals. But for demonstration i
    will give the code for a header and cpp file as simple as possible
    with no macros. I have also removed a number of compile time checks
    that are in the macro version.

    The basic concept is that the public implementation looks just like a
    lump of aligned memory (char array in this case) and defines calls to
    constructors/destructor and assignment operator which must not be
    implicit or inline. These are used to "initialise"/"destroy" that lump
    of memory. In the private implementation this lump of aligned memory
    actually looks like a class with a little bit left over. So the class
    itself looks different based on where you are looking at it from. I
    dont like this, but can not see a way of avoiding it.

    The macro implementation also uses different methods to align the
    array for different compilers. Following is a list of possible issues
    i can see with this:

    * Requires compiler specific implementation for variable alignment
    (There is a generic one that attempts to place a union of various data
    types just before the char array in order to try and align it
    correctly. This is wasteful of memory but i "think" will work for
    compilers i haven;t handled with variable alignment specifications
    like GCC below)

    * If the compiler produces padding between the Implementation instance
    in the cpp file and the char array that follows, then the size of the
    StackPImpl in the cpp file will differ from its size in the public
    causing all sorts of problems.

    - I dont know if this will ever occur. It will only occur if the
    sizeof(StackPImpl_IMPL) is such that the alignment of a char array
    will require padding before it.
    - Could be solved again by defining the char array in the private
    implementation as not being aligned

    * Will have size issues on systems where sizeof(char) != 1
    - May fix by defining size of array as array[SIZE / sizeof(char) +
    1]


    Would people be able to take a look at this and let me know if they
    can for-see any issues?

    Thanks,
    Brendon.

    --- header ---
    #define SIZE 8
    class StackPImpl
    {
    public:
    StackPImpl();
    ~StackPImpl();
    StackPImpl(const StackPImpl& right);
    StackPImpl& operator=(const StackPImpl& right);
    void Public();

    unsigned char pimpl_data[SIZE] __attribute__((aligned));
    };





    --- cpp implementation ---
    class StackPImpl_IMPL
    {
    public:
    int data;
    };


    #define SIZE 8
    class StackPImpl
    {
    public:
    StackPImpl();
    ~StackPImpl();
    void Public();

    StackPImpl_IMPL pimpl __attribute__((aligned));
    unsigned char pimpl_data[SIZE - sizeof(StackPImpl_IMPL)];
    };


    StackPImpl::StackPImpl()
    {
    }

    StackPImpl::~StackPImpl()
    {
    }

    // @@@Brendon Could not be bothered implementing operator= and copy
    for this eg

    void StackPImpl::public()
    {
    pimpl.data++;
    }
     
    Brendon Costa, Oct 31, 2007
    #16
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Icosahedron

    Pimpl Idiom

    Icosahedron, Nov 20, 2003, in forum: C++
    Replies:
    7
    Views:
    751
    Icosahedron
    Nov 22, 2003
  2. Debajit  Adhikary
    Replies:
    2
    Views:
    2,121
    Christopher Benson-Manica
    Jul 15, 2004
  3. Ken
    Replies:
    24
    Views:
    3,949
    Ben Bacarisse
    Nov 30, 2006
  4. Peteris Krumins
    Replies:
    2
    Views:
    527
    Peteris Krumins
    Aug 31, 2005
  5. jimmy

    friendship and pImpl idiom

    jimmy, Feb 8, 2006, in forum: C++
    Replies:
    3
    Views:
    466
    Alf P. Steinbach
    Feb 9, 2006
Loading...

Share This Page