EDIT: total re-edit because the original was becoming an unstructured mess :) Thanks for everyone's input so far; I hope I worked it into the text below.
I'm in search for a lazily-created shareable pointer. I have a hypothetical big class Thing. Things are big and thus costly to make, but while they are used everywhere in the code (shared, passed around liberally, modified, stored for later use, etc.), they are often not actually used in the end, so delaying their actual creation until they are actually accessed is preferable. Thing thus needs to be lazily-created, plus needs to be shareable. Lets call this encapsulating pointer wrapper SharedThing.
class SharedThing {
...
Thing* m_pThing;
Thing* operator ->() {
// ensure m_pThing is created
...
// then
return m_pThing
);
}
...
SharedThing pThing;
...
// Myriads of obscure paths taking the pThing to all dark corners
// of the program, some paths not even touching it
...
if (condition) {
pThing->doIt(); // last usage here
}
So far we've come up with four options:
typedef std::shared_ptr<Thing> SharedThing;
SharedThing newThing() {
return make_shared<Thing>();
}
...
// SharedThing pThing; // pThing points to nullptr, though...
SharedThing pThing(new Thing()); // much better
SharedThing pThing = newThing(); // alternative
The lack of score on points 1 and 6 is a killer here; no more option 1.
class SharedThing: public shared_ptr<Thing> {};
and override specific members to ensure that when the shared_ptr is dereferenced, it creates the Thing just in time.
This option is better than 1 and might be OK, but seems a mess and/or hackerish...
class SharedThing {
std::shared_ptr<Thing> m_pThing;
void EnsureThingPresent() {
if (m_pThing == nullptr) m_pThing = std::make_shared<Thing>();
}
public:
SharedThing(): m_pThing(nullptr) {};
Thing* operator ->() {
EnsureThingCreated();
return m_pThing.get();
}
}
and add extra wrapper methods alike for operator * and const versions.
This one fails miserably on 4, so this one's off as well.
class SharedThing {
typedef unique_ptr<Thing> UniqueThing;
shared_ptr<UniqueThing> m_pThing;
}
and add all other methods as in 3.1
This seems OK apart from the suggested performance (need to test, though).
class LazyCreatedThing {
Thing* m_pThing;
}
typedef shared_ptr<LazyCreatedThing> SharedThing;
SharedThing makeThing() {
return make_shared<LazyCreatedThing>();
}
and add all sorts of operator -> overloads to make LazyCreatedThing look like a Thing*
Failing miserably on 5 here makes this a no-no.
The best option so far thus seems 3.2; let's see what else we can come up with! :)
Use unique_ptr when you want to have single ownership(Exclusive) of the resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another. A shared_ptr is a container for raw pointers.
The difference is that std::make_shared performs one heap-allocation, whereas calling the std::shared_ptr constructor performs two.
When we create an object with new operator in shared_ptr there will be two dynamic memory allocations that happen, one for object from the new and the second is the manager object created by the shared_ptr constructor. Since memory allocations are slow, creating shared_ptr is slow when compared to raw pointer.
The purpose of shared_ptr is to manage an object that no one "person" has the right or responsibility to delete, because there could be others sharing ownership. So you shouldn't ever want to, either.
I would implement LazyThing
wrapper. I guess it is much easier to adapt Thing
interface, rather than std::shared_ptr
one.
class Thing
{
public:
void Do()
{
std::cout << "THING" << std::endl;
}
};
class LazyThing
{
public:
void Do()
{
getThing().Do();
}
private:
Thing& getThing()
{
if (!thing_)
thing_ = std::make_unique<Thing>();
return *thing_;
}
std::unique_ptr<Thing> thing_;
};
Now, you can use it with any smart pointer, or even create it on the stack:
LazyThing lazy;
auto sharedLazy = std::make_shared<LazyThing>();
auto uniqueLazy = std::make_unique<LazyThing>();
Or as in your example:
typedef std::shared_ptr<LazyThing> SharedLazyThing;
SharedLazyThing newThing() {
return std::make_shared<LazyThing>();
}
...
auto pThing = newThing();
UPDATE
If you want to guarantee shared semantic and do not bother with calling newThing()
or any other factory method, just give up on shared_ptr
interface. It is not needed there.
Implement SharedLazyThing
as a value type with shared semantic. The tricky stuff is that you need to add yet another level of indirection to provide lazy construction of shared Thing
object.
class SharedLazyThing
{
using UniqueThing = std::unique_ptr<Thing>;
public:
void Do()
{
getThing().Do();
}
private:
Thing& getThing()
{
if (!*thing_)
*thing_ = std::make_unique<Thing>();
return **thing_;
}
std::shared_ptr<UniqueThing> thing_ = std::make_shared<UniqueThing>();
};
Now, you can simply use SharedLazyThing
everywhere.
SharedLazyThing thing1;
SharedLazyThing thing2(thing1);
SharedLazyThing thing3 = thing1;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With