470,624 Members | 2,511 Online
Bytes | Developer Community
New Post

Home Posts Topics Members FAQ

Post your question to a community of 470,624 developers. It's quick & easy.

Pimpl idiom without dynamic memory allocation

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
Oct 17 '07 #1
14 2783
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
Oct 17 '07 #2
In article <47***********************@news.free.fr>,
Michael DOUBEZ <mi************@free.frwrote:
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
Oct 17 '07 #3
Daniel Lidström a écrit :
In article <47***********************@news.free.fr>,
Michael DOUBEZ <mi************@free.frwrote:
>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

Oct 17 '07 #4
Daniel Lidström <so******@microsoft.comwrote:
// 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.
Oct 17 '07 #5
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<42m_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

Oct 18 '07 #6
On Oct 17, 10:36 pm, "Daniel T." <danie...@earthlink.netwrote:
Daniel Lidström <someb...@microsoft.comwrote:
// 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:ja*********@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
Oct 18 '07 #7
In article <47***********************@news.free.fr>,
Michael DOUBEZ <mi************@free.frwrote:
>Daniel Lidström a écrit :
>In article <47***********************@news.free.fr>,
Michael DOUBEZ <mi************@free.frwrote:
>>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();
....
Oct 18 '07 #8
Yannick Tremblay wrote:
In article <47***********************@news.free.fr>,
Michael DOUBEZ <mi************@free.frwrote:
>>Daniel Lidström a écrit :
>>In article <47***********************@news.free.fr>,
Michael DOUBEZ <mi************@free.frwrote:

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
Oct 18 '07 #9
Kai-Uwe Bux <jk********@gmx.netwrote in
news:ff**********@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
Oct 18 '07 #10
Kai-Uwe Bux <jk********@gmx.netwrote in
news:ff**********@murdoch.acc.Virginia.EDU:
Yannick Tremblay wrote:
>In article <47***********************@news.free.fr>,
Michael DOUBEZ <mi************@free.frwrote:
>>>Daniel Lidström a écrit :
In article <47***********************@news.free.fr>,
Michael DOUBEZ <mi************@free.frwrote:

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?
Oct 18 '07 #11
Andre Kostur <nn******@kostur.netwrote 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
Oct 18 '07 #12
On Oct 18, 4:35 pm, Kai-Uwe Bux <jkherci...@gmx.netwrote:
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:ja*********@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

Oct 19 '07 #13
On Oct 18, 5:45 pm, Andre Kostur <nntps...@kostur.netwrote:
Kai-Uwe Bux <jkherci...@gmx.netwrote
innews:ff**********@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:ja*********@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

Oct 19 '07 #14
On Oct 18, 5:08 pm, Joe Greer <jgr...@doubletake.comwrote:
Kai-Uwe Bux <jkherci...@gmx.netwrote
innews:ff**********@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:ja*********@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

Oct 19 '07 #15

This discussion thread is closed

Replies have been disabled for this discussion.

Similar topics

7 posts views Thread by Icosahedron | last post: by
6 posts views Thread by Asfand Yar Qazi | last post: by
2 posts views Thread by Peteris Krumins | last post: by
4 posts views Thread by Simon Elliott | last post: by
9 posts views Thread by Edward Diener | last post: by
34 posts views Thread by Asfand Yar Qazi | last post: by
2 posts views Thread by Graham Reitz | last post: by
By using this site, you agree to our Privacy Policy and Terms of Use.