|
|
|
|
||||||
![]() |
|
|
LinkBack | Outils de la discussion |
|
|
#1 |
|
Messages: n/a
Hébergeur: |
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 |
|
|
|
#2 |
|
Messages: n/a
Hébergeur: |
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 |
|
|
|
#3 |
|
Messages: n/a
Hébergeur: |
In article <4716585e$0$25087$426a74cc@news.free.fr>,
Michael DOUBEZ <michael.doubez@free.fr> 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 |
|
|
|
#4 |
|
Messages: n/a
Hébergeur: |
Daniel Lidström a écrit :
> In article <4716585e$0$25087$426a74cc@news.free.fr>, > Michael DOUBEZ <michael.doubez@free.fr> 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 |
|
|
|
#5 |
|
Messages: n/a
Hébergeur: |
Daniel Lidström <somebody@microsoft.com> 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. |
|
|
|
#6 |
|
Messages: n/a
Hébergeur: |
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 |
|
|
|
#7 |
|
Messages: n/a
Hébergeur: |
On Oct 17, 10:36 pm, "Daniel T." <danie...@earthlink.net> wrote:
> Daniel Lidström <someb...@microsoft.com> 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:james.kanze@gmail.com 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 |
|
|
|
#8 |
|
Messages: n/a
Hébergeur: |
In article <47165e09$0$26728$426a74cc@news.free.fr>,
Michael DOUBEZ <michael.doubez@free.fr> wrote: >Daniel Lidström a écrit : >> In article <4716585e$0$25087$426a74cc@news.free.fr>, >> Michael DOUBEZ <michael.doubez@free.fr> 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(); .... |
|
|
|
#9 |
|
Messages: n/a
Hébergeur: |
Yannick Tremblay wrote:
> In article <47165e09$0$26728$426a74cc@news.free.fr>, > Michael DOUBEZ <michael.doubez@free.fr> wrote: >>Daniel Lidström a écrit : >>> In article <4716585e$0$25087$426a74cc@news.free.fr>, >>> Michael DOUBEZ <michael.doubez@free.fr> 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 |
|
|
|
#10 |
|
Messages: n/a
Hébergeur: |
Kai-Uwe Bux <jkherciueh@gmx.net> wrote in
news:ff7r0f$rg6$1@murdoch.acc.Virginia.EDU: > 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 |
|
|
|
#11 |
|
Messages: n/a
Hébergeur: |
Kai-Uwe Bux <jkherciueh@gmx.net> wrote in
news:ff7r0f$rg6$1@murdoch.acc.Virginia.EDU: > Yannick Tremblay wrote: > >> In article <47165e09$0$26728$426a74cc@news.free.fr>, >> Michael DOUBEZ <michael.doubez@free.fr> wrote: >>>Daniel Lidström a écrit : >>>> In article <4716585e$0$25087$426a74cc@news.free.fr>, >>>> Michael DOUBEZ <michael.doubez@free.fr> 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? |
|
|
|
#12 |
|
Messages: n/a
Hébergeur: |
Andre Kostur <nntpspam@kostur.net> 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 |
|
|
|
#13 |
|
Messages: n/a
Hébergeur: |
On Oct 18, 4:35 pm, Kai-Uwe Bux <jkherci...@gmx.net> 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:james.kanze@gmail.com 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 |
|
|
|
#14 |
|
Messages: n/a
Hébergeur: |
On Oct 18, 5:45 pm, Andre Kostur <nntps...@kostur.net> wrote:
> Kai-Uwe Bux <jkherci...@gmx.net> wrote > innews:ff7r0f$rg6$1@murdoch.acc.Virginia.EDU: > > 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:james.kanze@gmail.com 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 |
|
|
|
#15 |
|
Messages: n/a
Hébergeur: |
On Oct 18, 5:08 pm, Joe Greer <jgr...@doubletake.com> wrote:
> Kai-Uwe Bux <jkherci...@gmx.net> wrote > innews:ff7r0f$rg6$1@murdoch.acc.Virginia.EDU: > > 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:james.kanze@gmail.com 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 |
|
|
|
#16 |
|
Messages: n/a
Hébergeur: |
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++; } |
|
![]() |
| Outils de la discussion | |
|
|