I have a Singleton
class that manages a container of Item
s, exposing public functions that allow Item
s to be added or removed from the container.
class Item;
typedef std::shared_ptr<Item> ItemPtr;
class Singleton
{
public:
static Singleton& Instance()
{
static std::unique_ptr<Singleton> Instance(new Singleton);
return *Instance;
}
void Add(ItemPtr item)
{
mContainer.push_back(item);
}
void Remove(ItemPtr item)
{
for (auto it = mContainer.begin(); it != mContainer.end(); it++)
if (*it == item)
mContainer.erase(it);
}
private:
std::vector<ItemPtr> mContainer;
};
I'd like Item
to have the ability to add itself to the Singleton
container via an Add()
method, and remove itself from the container upon its destruction.
class Item
{
public:
Item() {}
~Item()
{
Singleton::Instance().Remove(ItemPtr(this));
}
void Add()
{
Singleton::Instance().Add(ItemPtr(this));
}
};
When I run the example below, I get a crash on Singleton::Remove()
, specifically a EXC_BAD_ACCESS
on mContainer.begin()
.
int main()
{
Item* a = new Item();
Item* b = new Item();
a->Add();
b->Add();
delete a;
delete b;
}
This seems to indicate that mContainer
no longer exists. Looking at the call stack, I can also see one of the root call stack frames is the destructor Singleton::~Singleton()
, which would explain why mContainer
is no longer there.
I've tried a different approach : instead of using std::shared_ptr<Item>
I simply used raw pointers (i.e., Item*
) with the appropriate substitutions in the code. It worked without problems.
My questions are:
Item
objects is only released by the shared_ptr
after the destruction of Singleton
, which causes the error. Is this correct?Singleton
is of shared_ptr<Item>
?The wisdom of doing this in the first place notwithstanding, what you want can be achieved if you're willing to use, and abide by the restrictions of, std::enabled_shared_from_this
. See below:
#include <iostream>
#include <algorithm>
#include <memory>
#include <vector>
struct Item;
typedef std::shared_ptr<Item> ItemPtr;
class Singleton
{
private:
Singleton() {}
public:
static Singleton &Instance()
{
static Singleton s;
return s;
}
void Add(ItemPtr item)
{
mContainer.emplace_back(std::move(item));
}
void Remove(const ItemPtr& item)
{
mContainer.erase(
std::remove(mContainer.begin(), mContainer.end(), item),
mContainer.end());
}
void Clear()
{
mContainer.clear();
}
private:
std::vector<ItemPtr> mContainer;
};
// note derivation. this means you can get a std::shared_ptr<Item>
// via `shared_from_this` , but it also means the object itself
// MUST be an actual shared object to begin with.
struct Item : public std::enable_shared_from_this<Item>
{
void Add()
{
Singleton::Instance().Add(shared_from_this());
}
};
int main()
{
ItemPtr a = std::make_shared<Item>();
ItemPtr b = std::make_shared<Item>();
// add to the singleton container
a->Add();
b->Add();
// report reference count of 'a'
std::cout << "before removal 'a' has " << a.use_count() << " references\n";
Singleton::Instance().Remove(a);
std::cout << "after removal 'a' has " << a.use_count() << " references\n";
}
Output
before removal 'a' has 2 references
after removal 'a' has 1 references
The most important part of this is the creation of a
and b
in main
. Notice they are, in fact, managed by std::shared_ptr
enshrouding from inception. This is required for std::enable_shared_from_this
to work correctly. The rest is fairly straight forward. The ability to get a reference-bumped std::shared_ptr
from within the body of any member of Item
is done via the shared_from_this()
member provided from the base class std::enable_shared_from_this
.
In short, taking this approach will work for you, but at no point can you use shared_from_this()
unless the object it is being fired upon is already managed by a std::shared_ptr
in the first place. Keep that in mind.