Reputation: 1090
I need to keep track of all objects created of a specific class and I need to access them using a identifier string. The following code does pretty much exactly what I need. The class NamedObject has a static member m_object_by_name, which maps the name (string) to the object, the constructor adds every created object to the map and the destructor removes deleted objects from the map.
#include <map>
#include <string>
#include <iostream>
using namespace std;
class NamedObject
{
public:
static NamedObject *object_by_name(const string &name) {
return m_object_by_name[name];
}
NamedObject(const string &name) : m_name(name) {
m_object_by_name[m_name] = this;
}
~NamedObject() {
m_object_by_name.erase(this->m_name);
}
const string &name() const{
return m_name;
}
private:
string m_name;
static map<string, NamedObject *> m_object_by_name;
};
map<string, NamedObject *> NamedObject::m_object_by_name;
int main ()
{
new NamedObject("name1");
new NamedObject("name2");
NamedObject *obj1 = NamedObject::object_by_name("name1");
NamedObject *obj2 = NamedObject::object_by_name("name2");
cout << obj1->name() << endl;
cout << obj2->name() << endl;
}
Now I have several classes whose objects need to be accessed by their name. Inheriting form the above NamedObject class of course has the problem that all these classes would share their names (e.g., I cannot have two objects of different classes but with the same name), as they share the map m_objects_by_name. Moreover, when accessing objects using the object_by_name() method, I always have to cast from NamedObject to the actual class.
The solution for this problem I use at the moment can be seen in the following code. However, I am not really satisfied with this solution (see comments below). The template class NamedObjectStore is now responsible for storing all objects of class T. Moreover, there is a base class handling the properties of having a name and a derived class that is really used. The derived class has a static NamedObjectStore object where it adds its objects on creation and removes them on deletion.
#include <map>
#include <string>
#include <iostream>
using namespace std;
template <class T>
class NamedObjectStore
{
public:
void add_object(T *obj) {
m_object_by_name[obj->name()] = obj;
}
void rem_object(T *obj) {
m_object_by_name.erase(obj->name());
}
T *object_by_name(const string &name) {
return m_object_by_name[name];
}
private:
map<string, T *> m_object_by_name;
};
class BaseNamedObject
{
public:
BaseNamedObject(const string &name) : m_name(name) {
}
const string &name() const {
return m_name;
}
private:
string m_name;
};
class DerivedNamedObject : public BaseNamedObject
{
public:
static NamedObjectStore<DerivedNamedObject> store;
DerivedNamedObject(const string &name) : BaseNamedObject(name) {
store.add_object(this);
}
~DerivedNamedObject() {
store.rem_object(this);
}
};
NamedObjectStore<DerivedNamedObject> DerivedNamedObject::store;
int main ()
{
new DerivedNamedObject("name1");
new DerivedNamedObject("name2");
DerivedNamedObject *obj1 = DerivedNamedObject::store.object_by_name("name1");
DerivedNamedObject *obj2 = DerivedNamedObject::store.object_by_name("name2");
cout << obj1->name() << endl;
cout << obj2->name() << endl;
}
On the positive side, the implementation of what makes an object a named object (i.e., the name()-function) is done in the base class BaseNamedObject. Also the implementation of the structure storing all objects lies in the NamedObjectStore class and is hidden behind its methods. This allows me to easily change both of these implementations if desired, without touching all the derived classes.
On the negative side, I still have to type the same stuff over and over again. More precisely, for every derived class (like DerivedNamedObject), I have to declare and to define the static member store, I have to add objects to the store in the constructor and remove them from the store in the destructor.
So here comes my question: is there a nicer way to solve this problem? Or do I just have to live with these four lines of code in every derived class?
Hoping for some inspiring suggestions :-)
Thomas
Upvotes: 1
Views: 1854
Reputation: 19032
As stated in my comment, you can solve this using the curiously recurring template pattern. The code below uses your original example, templated on the type actually being stored:
#include <map>
#include <string>
#include <iostream>
using namespace std;
template<class T>
class NamedObject
{
public:
static NamedObject *object_by_name(const string &name) {
return m_object_by_name[name];
}
NamedObject(const string &name) : m_name(name) {
m_object_by_name[m_name] = this;
}
virtual ~NamedObject() {
m_object_by_name.erase(this->m_name);
}
const string &name() const{
return m_name;
}
private:
string m_name;
static map<string, NamedObject *> m_object_by_name;
};
template <class T>
map<string, NamedObject<T> *> NamedObject<T>::m_object_by_name;
class A : public NamedObject<A>
{
public:
A(const std::string& name) : NamedObject(name)
{}
};
class B : public NamedObject<B>
{
public:
B(const std::string& name) : NamedObject(name)
{}
};
int main()
{
new A("Test");
new B("Test");
auto one = A::object_by_name("Test");
auto two = B::object_by_name("Test");
cout << one << " - " << one->name() << "\n";
cout << two << " - " << two->name() << "\n";
delete two;
delete one;
}
Upvotes: 2