Creation of Objects for Using With Std::shared_ptr
In this post, I will tell about an optimization I have recently discovered and successfully applied in my project. It is very simple to apply but yields very good results - in terms of both performance and memory footprint.
Let's start with the usual way shared_ptr
s are created:
class Test
{
public:
Test(int dummy) {}
}
...
std::shared_ptr<Test> pTest(new Test(1));
Now let's look step by step at what's going on here:
- A new object of class
Test
is allocated and constructed. Note that it is allocated on the heap, and each heap allocation incurs memory overhead above sizeof(Test)
. Allocation overheads are discussed in more detail in this blog post. - Then a pointer is passed to the constructor of
std::shared_ptr<Test>
which takes ownership of the newly created object.
The mentioned constructor of shared_ptr
basically does two things: it assigns its internal pointer to the object that it will hold, and it allocates (again on the heap!) a shared buffer that will be used to hold reference counters. As you can see, here's another memory allocation, another performance and memory footprint hit. So the more efficient way of doing it is:
std::shared_ptr<Test> pTest = std::make_shared<Test>(1);
There are a few benefits of doing this:
- There is only one memory allocation - both reference counters and the object itself are allocated in one buffer (however, this is implementation-dependent, so this is the case for Visual C++ 10).
- There is a constructor of
std::shared_ptr<T>
that takes an R-value reference as an argument, making this construct potentially more efficient.
What are the drawbacks here you might ask? There are only a few of these:
- As Visual C++ 10 (and beta of VC11) does not support variadic templates number of arguments passed to object's constructor is limited to 10 (and in VC11 to 5 with default settings)
std::make_shared
's from the boost library does not support this optimization and even in the latest version available now (1.49.0) allocates two separate buffers - one for the object and one for reference counters
So anyway, if you have boost::shared_ptr
s and you are using boost starting from 1.36.0 in your project (that's the version that make_shared
was copied from tr1 to boost library), it might still make sense to migrate to make_shared
even if you won't get the benefits right away.
Is It That Simple?
No, there is one small pitfall I hit when I migrated my project to using std::make_shared
. When the object is being created by some factory class, the constructor is declared private
and the factory class is declared as a friend
. The problem lies in the fact that when the factory creates a new object with make_shared
, the constructor is called from some unknown implementation-specific class, and this call fails to compile as we can't declare it a friend
of our class. The solution is not very elegant (at least for my taste), but it works, and helps when this optimization is applied to make a real difference in performance. Here is a sample code:
class Obj;
typedef std::shared_ptr ObjPtr;
class Obj
{
...
private:
struct HideMe {}
friend class ObjFactory;
public:
Obj(HideMe, int param)
{ ... }
}
class ObjFactory
{
public:
static ObjPtr CreateObj()
{
return std::make_shared(Obj::HideMe(), param) ;
}
}
So the actual constructor that is being called by ObjFactory
is public
, but no other object could invoke it due to the hidden structure Obj::HideMe
.
Happy optimizing!
History
- 15th June, 2012: Initial version