I try to modify an existing object pool class so that I am able to pass an instance creator class as an argument to the object pool. Basically I want to be able to keep the actual object construction out of the memory pool so I have more freedom in what way I create instances to be pooled.
Here is the object pool definition:
template <
typename T,
typename InstanceCreator = DefaultInstanceFactory<T>
>
class ObjectPool : private noncopyable {
...
}
So I would create an ObjectPool like this
ObjectPool<int> intPool((DefaultInstanceFactory<int>()));
or
ObjectPool<IntClass, IntClass::InstanceFactory> intPool (IntClass::InstanceFactory (1));
The default Instance creator looks like this
template <typename T>
class DefaultInstanceFactory {
public:
T * operator ()() const {
return new T;
}
};
Inside that ObjectPool class is a nested class that stores the items
class PooledItem {
public:
char data[OBJECT_SIZE];
PooledItem * next;
bool initialized;
PooledItem()
: initialized(false) {}
~PooledItem() {
// --- call T destructor
if (initialized)
cast()->~T();
}
T * cast() {
return reinterpret_cast<T *>(data);
};
};
There is a borrowObject method to acquire an object and here is my actual problem:
T * borrowObject() {
PooledItem * item = getItem();
T * obj = item->cast();
if (! item->initialized) {
// old original line, call the defaut constructor of T
new (obj) T();
// how to integrate the external Instance Creator at this point?
//new (instCreator_ ()) T(1);
//instCreator_ ();
item->initialized = true;
}
if (obj == NULL) {
throw ObjectPoolException(
"Object is NULL!", __FILE__, __LINE__, __FUNCTION__);
}
return obj;
}
In above method I marked the actual problem lines. I have no idea how to replace the placement new new (obj) T() line with the external Instance creator, to just reuse that memory.
For completeness, the method for returning an object to the pool looks like this
void returnObject(T * obj) {
// --- Get containing PooledItem pointer
PooledItem * item = reinterpret_cast<PooledItem *>(obj);
// --- Make sure object came from this pool
if (item->next != reinterpret_cast<PooledItem *>(this)) {
// throw Exception
}
// --- Destroy object now if we want to reconstruct it later
if (destroyOnRelease) {
item->cast()->~T();
item->initialized = false;
}
Could anybody give me some help how to modify the methods so that the external Instance Creator gets integrated properly? I do not know up to now, if I need to change something in the returnObject method, up to now I think not.
Appreciate your help!
Sounds like you need to change InstanceFactory's signature to take a pointer
template <typename T>
class DefaultInstanceFactory {
public:
void operator ()(T* out) const {
return new(out) T;
}
};
Related
Let there be a C++ library (let's call it lib) which gets included as a static library in an application (let's call it app). Within the lib there's a base class node. Each subclass of a node is identified by a UUID.
I employ a self registering pattern to ensure that new classes register themselves at the factory. The factory allows to build a node subclass object based on the provided UUID. The app builds objects through the lib's factory::build() function.
My factory is based on the code of this brilliant blog post.
I've adapted this code to use a UUID (using boost.uuid) instead of a string as all the classes being created need a UUID assigned by them anyway (for external dependency reasons).
The problem I am hitting is that if I do not "manually" create an object instance of each node_template subclass (i.e. A and B), the factory::m_generators map is empty.
This is of course due to the fact that the various node-subclasses were never instantiated and therefore never registered themselves.
Here's my runnable minimum example (live demo at coliru):
#include <iostream>
#include <unordered_map>
#include <functional>
#include <memory>
#include <boost/functional/hash.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/string_generator.hpp>
class node;
/**
* #brief The factory class to build #p node items.
*/
class factory
{
public:
using key_type = boost::uuids::uuid;
using key_hash = boost::hash<key_type>;
using generator = std::function<std::unique_ptr<node>()>;
template<typename Derived>
struct registrar
{
registrar(const key_type& key)
{
factory::instance().register_generator(key, [](){
return std::make_unique<Derived>();
});
}
registrar(const std::string& uuid_string)
{
try {
boost::uuids::string_generator gen;
registrar(gen(uuid_string));
} catch (...) {
;
}
}
};
static factory& instance() noexcept
{
static factory f;
return f;
}
bool register_generator(const key_type& key, generator&& generator)
{
auto [it, emplaced] = m_generators.try_emplace(key, std::move(generator));
return emplaced;
}
[[nodiscard]] std::unique_ptr<node> build(const key_type& key) const
{
if (const auto& it = m_generators.find(key); it not_eq m_generators.cend())
return it->second();
return nullptr;
}
[[nodiscard]] std::unique_ptr<node> build(const char* uuid_string) const noexcept
{
try {
boost::uuids::string_generator gen;
return build(gen(uuid_string));
} catch (...) {
return nullptr;
}
}
private:
std::unordered_map<key_type, generator, key_hash> m_generators;
factory() = default;
factory(const factory& other) = default;
factory(factory&& other) = default;
virtual ~factory() = default;
};
/**
* #brief The node base class.
*/
struct node
{
node(const std::string& uuid_string) :
m_uuid_string(uuid_string)
{
}
[[nodiscard]] const std::string& uuid_string() const noexcept {
return m_uuid_string;
}
private:
std::string m_uuid_string;
};
/**
* #brief A template for #p node subclasses.
*/
template <class derived>
struct node_template :
node,
factory::registrar<derived>
{
node_template(const std::string& uuid_string) :
node(uuid_string),
factory::registrar<derived>(uuid_string)
{
}
};
struct A : node_template<A> {
A() : node_template("63cb8eeb-b90b-46c7-aaa8-3a349fcba3c5") { }
};
struct B : node_template<B> {
B() : node_template("1f24abfc-936f-4524-ae3b-cc346335ecbb") { }
};
static void build_and_print(const std::string& uuid_string)
{
if (auto node = factory::instance().build(uuid_string.c_str()); node)
std::cout << "node.uuid_string() = " << node->uuid_string() << std::endl;
else
std::cout << "Cannot build node object: Unknown UUID." << std::endl;
}
int main(void)
{
////////////////////////////////////////////////////////////////////////////////////////////////////
/// PROBLEM: If I do not construct these objects, they never register themselves at the factory. ///
////////////////////////////////////////////////////////////////////////////////////////////////////
#if 1
A a;
B b;
#endif
// A
build_and_print("63cb8eeb-b90b-46c7-aaa8-3a349fcba3c5");
// B
build_and_print("1f24abfc-936f-4524-ae3b-cc346335ecbb");
// Unknown UUID
build_and_print("9b20cc29-c7ca-4796-acb2-6ca6b80fa934");
return 0;
}
As long as I keep the object instantiations in line 136 and 137 I am able to build further objects through the factory. But once I remove then (eg. change line 135 to #if 0) the factory generators map is empty.
I think that I understand the problem as in that the class never gets to register itself as there's never an object being constructed. However, I am not sure how to fix the problem.
Currently there's a very ugly header file in lib which gets included by app. The header creates a dummy object of each class. This is of course anything but nice and also kind of defeats the entire purpose of a self registering class.
What am I missing here?
From the blog post, you are missing the static member registered (also called "// The really fun part"). Having and instantiating such a static variable in the base class forces it to be instantiated in all derived classes and this will register the class as a side effect.
EDIT: There is another very small but very important piece of code in the blog post:
Registrar() : Base(Key{}) { (void)registered; }
This will ensure that registered is used. Because a static variable is only instantiated the first time it is used, otherwise the function is not called.
In your case, adding the following to node_template should work:
template <class derived>
struct node_template :
node,
factory::registrar<derived>
{
node_template(const std::string& uuid_string) :
node(uuid_string),
factory::registrar<derived>(uuid_string)
{
(void) registered;
}
static bool do_register() {
derived d; // I am not sure if one should in some way force this to not be optimized away.
return true;
}
inline static bool registered = do_register();
};
I am having a strange problem instantiating a structure living inside a class, where in construction it calls the destructor (several times) and even calls the parent object destructor.
Class with structure:
class Model {
public:
struct StepModelIO {
StepModelIO(Model model, ...) {DoConstruction(Model model, ...);}
StepModelIO(const StepModelIO &other) {DoConstruction(Model model, ...); }
~StepModelIO() {}
DoConstruction() {
...
}
}
Model(...) {
...
}
Model(const Model &other) {DoConstruction(...);}
~Model() {
...
}
private:
DoConstruction(...) {
}
}
Calling function:
void main() {
Model::StepModelIO stepArgs = Model::StepModelIO(...);
}
The resulting set of calls, with 'object' being the StepModelIO and 'parent' being the Model:
Construct parent w/ copy constructor
Construct object
Destruct parent
Construct object w/ copy constructor
Construct parent w/ copy constructor
Construct object
Destruct parent
Destruct object
Destruct object (again...)
Destruct parent
Unsurprisingly the resulting structure (a StepModelIO) is not in a good state after this all happens, and the path seemed ridiculous. I have the structure housed like this to use the same generic at the parent Model object, which may explain some of the issues.
I have tried (perhaps naively) to use the 'rule of three' on constructors and destructors, it's possible I've munged this up badly.
Edit: Full code
template<typename U, typename V>
class Model{
public:
struct StepModelIO {
Model<U, V> model;
U u;
V v;
StepModelIO() {}
StepModelIO(Model<U, V> model, U u, V v) {
this->model = model;
this->u = u;
this->v = v;
}
StepModelIO (const StepModelIO &other) {
StepModelIO(other.model, other.u, other.v);
}
~StepModelIO() {
}
};
Model(char * libraryPath) {DoConstruction(libraryPath);}
Model() {}
Model (const Model &other) {
DoConstruction(other.m_LibraryPath);
}
~Model() {
this->Stop();
}
void Init() {
if (!this->m_Initialised) {
this->ModelInit();
m_Initialised = true;
}
}
void Stop() {
if (this->m_Initialised) {
this->ModelStop();
m_Initialised = false;
}
}
void Restart() {
this->ModelRestart();
}
void Step(U u, V v) {
ModelStep(u, v);
}
private:
char* m_LibraryPath;
HINSTANCE m_ModelDLL;
bool m_Initialised;
typedef int (__cdecl * EmptyModelFunctionPointer)(); // Interpret integer as C code pointer named 'EmptyModelFunctionPointer'
typedef int (__cdecl * ModelFunctionPointer)(U u, V v);
EmptyModelFunctionPointer ModelInit;
EmptyModelFunctionPointer ModelStop;
EmptyModelFunctionPointer ModelRestart;
ModelFunctionPointer ModelStep;
virtual void DoConstruction(char * libraryPath){
this->m_Initialised = false;
this->m_LibraryPath = libraryPath;
this->m_ModelDLL = LoadLibrary(libraryPath);
this->ModelInit = GetFunction<EmptyModelFunctionPointer>(m_ModelDLL, "Init");
this->ModelStop = GetFunction<EmptyModelFunctionPointer>(m_ModelDLL, "Stop");
this->ModelRestart = GetFunction<EmptyModelFunctionPointer>(m_ModelDLL, "Restart");
this->ModelStep = GetFunction<ModelFunctionPointer>(m_ModelDLL, "Step");
}
template<typename pointerType>
pointerType GetFunction(HINSTANCE modelLibrary, char * functionName){
return (pointerType)GetProcAddress(HMODULE (modelLibrary),functionName);
}
};
Caller:
StepModelIO<Type_1*, Type_2*> stepArgs = StepModelIO<Type_1*, Type_2*>(newModel, &a, &b[0]);
You're passing things by value, which will result in temporary objects being constructed and destructed. Pass them by const reference instead.
change
StepModelIO(Model model, ...)
to
StepModelIO(const Model &model, ...)
You've now changed the code. So you really want this, I think.
StepModelIO(const Model<U, V> &model, const U &u, const V &v)
I have notices tat inside the class StepModelIO you have a member Model<U, V> model; so for each instance of class StepModelIO the destructor of model will be caled also; first ~StepModelIO() and second ~Model<U, V>()
so given the code you provided:
Model::StepModelIO stepArgs = Model::StepModelIO(...);
This has two objects of the type StepModelIO. One is the on in the right (rvalue) and the second one is stepArgs.
First the destructor for the one in the right is called resulting in:
1:Destruct StepModelIO
2:Destruct Model
And when the destruction of stepArgs occurs :
3:Destruct StepModelIO
4:Destruct Model
I have a map of addresses that allows me to store arbitrary data with objects. Basically, a library I'm writing has a templated function that winds up storing arbitrary data with objects.
std::map<void *, MyUserData>
This works, until the object passed in is destroyed, leaving its user data in the map. I want the associated user data to be removed as well, so I need to somehow listen for the destructor of the passed in object,
Some example code that illustrates the problem:
#include <map>
#include <memory>
struct MyUserData
{
int someNum;
};
std::map<void *, MyUserData> myMap;
template <typename T>
registerObject<T>(const std::shared_ptr<T> & _object)
{
static inc = 0;
myMap[(void *)&_object->get()].someNum = inc++;
}
struct MyObject
{
int asdf;
};
int main(int _argc, char ** _argv)
{
auto obj = std::make_shared<MyObject>();
obj->asdf = 5;
registerObject(obj);
obj = 0;
//The user data is still there. I want it to be removed at this point.
}
My current solution is to set a custom deleter on the shared_ptr. This signals me for when the object's destructor is called, and tells me when to remove the associated user data. Unfortunately, this requires my library to create the shared_ptr, as there is no "set_deleter" function. It must be initialized in the constructor.
mylib::make_shared<T>(); //Annoying!
I could also have the user manually remove their objects:
mylib::unregister<T>(); //Equally annoying!
My goal is to be able to lazily add objects without any prior-registration.
In a grand summary, I want to detect when the object is deleted, and know when to remove its counterpart from the std::map.
Any suggestions?
P.S. Should I even worry about leaving the user data in the map? What are the chances that an object is allocated with the same address as a previously deleted object? (It would end up receiving the same user data as far as my lib is concerned.)
EDIT: I don't think I expressed my problem very well initially. Rewritten.
From you code example, it looks like the external interface is
template <typename T>
registerObject<T>(const std::shared_ptr<T> & _object);
I assume there is a get-style API somewhere. Let's call this getRegisteredData. (It could be internal.)
Within the confines of the question, I'd use std::weak_ptr<void> instead of void*, as std::weak_ptr<T> can tell when there are no more "strong references" to the object around, but won't prevent the object from being deleted by maintaining a reference.
std::map<std::weak_ptr<void>, MyUserData> myMap;
template <typename T>
registerObject<T>(const std::shared_ptr<T> & _object)
{
static inc = 0;
Internal_RemoveDeadObjects();
myMap[std::weak_ptr<void>(_object)].someNum = inc++;
}
template <typename T>
MyUserData getRegisteredData(const std::shared_ptr<T> & _object)
{
Internal_RemoveDeadObjects();
return myMap[std::weak_ptr<void>(_object)];
}
void Internal_RemoveDeadObjects()
{
auto iter = myMap.cbegin();
while (iter != myMap.cend())
{
auto& weakPtr = (*iter).first;
const bool needsRemoval = !(weakPtr.expired());
if (needsRemoval)
{
auto itemToRemove = iter;
++iter;
myMap.erase(itemToRemove);
}
else
{
++iter;
}
}
}
Basically, std::weak_ptr and std::shared_ptr collaborate and std::weak_ptr can detect when there are no more std::shared_ptr references to the object in question. Once that is the case, we can remove the ancillary data from myMap. I'm using the two interfaces to myMap, your registerObject and my getRegisteredData as convenient places to call Internal_RemoveDeadObjects to perform the clean up.
Yes, this walks the entirety of myMap every time a new object is registered or the registered data is requested. Modify as you see fit or try a different design.
You ask "Should I even worry about leaving the user data in the map? What are the chances that an object is allocated with the same address as a previously deleted object?" In my experience, decidedly non-zero, so don't do this. :-)
I'd add a deregister method, and make the user deregister their objects. With the interface as given, where you're stripping the type away, I can't see a way to check for the ref-count, and C++ doesn't provide a way to check whether memory has been deleted or not.
I thought about it for a while and this is as far as I got:
#include <memory>
#include <map>
#include <iostream>
#include <cassert>
using namespace std;
struct MyUserData
{
int someNum;
};
map<void *, MyUserData> myMap;
template<class T>
class my_shared_ptr : public shared_ptr<T>
{
public:
my_shared_ptr() { }
my_shared_ptr(const shared_ptr<T>& s) : shared_ptr<T>(s) { }
my_shared_ptr(T* t) : shared_ptr<T>(t) { }
~my_shared_ptr()
{
if (unique())
{
myMap.erase(get());
}
}
};
template <typename T>
void registerObject(const my_shared_ptr<T> & _object)
{
static int inc = 0;
myMap[(void *)_object.get()].someNum = inc++;
}
struct MyObject
{
int asdf;
};
int main()
{
{
my_shared_ptr<MyObject> obj2;
{
my_shared_ptr<MyObject> obj = make_shared<MyObject>();
obj->asdf = 5;
registerObject(obj);
obj2 = obj;
assert(myMap.size() == 1);
}
/* obj is destroyed, but obj2 still points to the data */
assert(myMap.size() == 1);
}
/* obj2 is destroyed, nobody points to the data */
assert(myMap.size() == 0);
}
Note however that it wouldn't work if you wrote obj = nullptr; , or obj.reset(), since the object isn't destroyed in those cases (no destructor called). Also, you can't use auto with this solution.
Also, be careful not to call (void *)&_object.get() like you were doing. If I'm not terribly wrong, by that statement you're actually taking the address of the temporary that _object.get() returns, and casting it to void. That address, however, becomes invalid instantly after.
This sounds like a job for... boost::intrusive (http://www.boost.org/doc/libs/1_53_0/doc/html/intrusive.html)! I don't think the current interface will work exactly as it stands though. I'll try to work out a few more details a little later as I get a chance.
You can just do
map.erase(map.find(obj));
delete obj;
obj = 0;
this will call the destructor for your user data and remove it from the map.
Or you could make your own manager:
class Pointer;
extern std::map<Pointer,UserData> data;
class Pointer
{
private:
void * pointer;
public:
//operator ()
void * operator()()
{
return pointer;
}
//operator =
Pointer& operator= (void * ptr)
{
if(ptr == 0)
{
data.erase(data.find(pointer));
pointer = 0;
}
else
pointer = ptr;
return *this;
}
Pointer(void * ptr)
{
pointer = ptr;
}
Pointer()
{
pointer = 0;
}
~Pointer(){}
};
struct UserData
{
static int whatever;
UserData(){}
};
std::map<Pointer,UserData> data;
int main()
{
data[Pointer(new UserData())].whatever++;
data[Pointer(new UserData())].whatever++;
data[Pointer(new UserData())].whatever++;
data[Pointer(new UserData())].whatever++;
Pointer x(new UserData());
data[x].whatever;
x = 0;
return 0;
}
What I want to do is to write a small Manager/Handler class. A Manager distributes and manages Handles. Such a handle could be for example a simple filehandle.
If a consumer wants to get a handle which already exists, the manager simply returns a shared_ptr. If the handle does not exist, the manager creates a new handle and then returns the shared_ptr.
Inside the Manager, those shared_ptr's are stored in a simple STL-Map.
If the last shared_ptr, which was assigned gets deleted, I want my manager to remove the related map-element, so that the handler object automatically gets destructed.
This sounds a bit like garbage-collection(e.g. worker thread, which checks the usage count of the pointers), but I am sure it can be done more elegantly.
How do I pass a reference of the manager instance to the handler object? (e.g. sth. like passing a unique_ptr(this) to the constructor of a new handler)
#include <memory>
#include <iostream>
#include <map>
using namespace std;
/*
* Simple handler class, that actually does nothing.
* This could be e.g. a Filehandler class or sth. like that
*/
class Handler {
private:
int i;
public:
Handler(int i) :i(i) {}
~Handler() {}
// Say who you are.
void print(void) { cout << "I am handler # " << i << endl; }
};
/*
* This is the "manager" class, that manages all handles. A handle is identified
* by an integer value. If a handle already exists, the Manager returns a shared_ptr,
* if it does not exist, the manager creates a new handle.
*/
class Manager {
private:
map<int, shared_ptr<Handler> > handles;
public:
Manager() {}
~Manager() {}
shared_ptr<Handler> get_handler(int identifier) {
shared_ptr<Handler> retval;
auto it = handles.find(identifier);
if(it != handles.end() ) {
retval = it->second;
} else {
retval = shared_ptr<Handler>(new Handler(identifier));
handles.insert( pair<int, shared_ptr<Handler>>(identifier, retval) );
}
return retval;
}
};
int main(int argc, char** argv) {
Manager m;
// Handler 13 doesn't exist, so it gets allocated
auto h = m.get_handler(13);
// Manager knows about handler 13, so it returns the already existing shared_ptr
auto i = m.get_handler(13);
h.reset(); // Well... Let's assume we don't need h any more...
// do some stuff...
i->print();
// ...
i.reset(); // We also loose i. This is exactly the point where i want the manager to forget about the handle 13
return 0;
}
You may want to hold non-owning pointers in your manager to keep track of existing handles, and give away owning shared_ptr with a custom deleter. The custom deleter would make sure the corresponding observing pointer in the manager is removed when the object eventually gets destroyed.
I called this pattern Tracking Factory, and here is how it works. Given an object class (would be Handler in your case):
class object
{
public:
size_t get_id() const
{
return _id;
}
private:
friend class tracking_factory;
object(size_t id) : _id(id) { }
size_t _id = static_cast<size_t>(-1);
};
I define a class which creates instances of object and stores non-owning references (weak_ptrs) to them. This class is the only class through which instances of object can be created - this is why the constructor of object is private, and tracking_factory is declared as friend in order to be able to access it:
class tracking_factory
{
public:
std::shared_ptr<object> get_object(size_t id,
bool createIfNotFound = true)
{
auto i = std::find_if(
begin(_objects),
end(_objects),
[id] (std::pair<size_t const, std::weak_ptr<object>> const& p)
-> bool
{
return (p.first == id);
});
if (i != end(_objects))
{
return i->second.lock();
}
else if (createIfNotFound)
{
return make_object(id);
}
else
{
return std::shared_ptr<object>();
}
}
size_t count_instances() const
{
return _objects.size();
}
private:
std::shared_ptr<object> make_object(size_t id)
{
std::shared_ptr<object> sp(
new object(id),
[this, id] (object* p)
{
_objects.erase(id);
delete p;
});
_objects[id] = sp;
return sp;
}
std::map<size_t, std::weak_ptr<object>> _objects;
};
Then, the rest of program will obtain shared_ptrs to objects through the object_factory: if an object with the desired characteristics (an id member here) has been created already, a shared_ptr to it will be returned without a new object being instantiated. Here is some code to test the functionality:
#include <iostream>
int main()
{
tracking_factory f;
auto print_object_count = [&f] ()
{
std::cout << "Number of objects: " << f.count_instances() << std::endl;
};
print_object_count();
auto p1 = f.get_object(42);
print_object_count();
{
auto p2 = f.get_object(42);
print_object_count();
p1 = f.get_object(0);
print_object_count();
}
print_object_count();
p1.reset();
print_object_count();
}
Finally, here is a live example.
Store std::weak_ptr objects in the map; they don't retain ownership, so when the last std::shared_ptr object goes away the resource will be destroyed. But they do keep track of whether there are any remaining std::shared_ptr objects that point to the original object, so putting them in the map lets you check later whether there is still a resource there.
I'm trying to map some structs to some other instances, like this:
template <typename T>
class Component {
public:
typedef std::map<EntityID, T> instances_map;
instances_map instances;
Component() {};
T add(EntityID id) {
T* t = new T();
instances[id] = *t;
return *t;
};
};
Then I use it like this:
struct UnitInfos {
int owner_id;
int health;
float x, y;
};
class LogicComponent : public Component<UnitInfos> {};
The problem is that when it later retrieve data later on, like this:
comp.instance[id];
I get a breand new object with properties initialized at default values.
Is there something inherently wrong with this piece of code, or am I leaving out information about the problem?
As per #aaa suggestion, i change the code to
typedef std::map<EntityID, T> instances_map;
instances_map instances;
T& add(EntityID id) {
instances[id] = T();
return instances[id];
};
but when I access it
UnitInfos &info = logic_c.instances[id];
the value of info.x is still 0. Any pointers?
The problem was how I stored the reference to LogicComponent in another class. using LogicComponent logic_c; instead of LogicComponent& logic_c;. It now works, but I'm storing pointers in the map (instead of #aaa's suggestion). Is this a bad idea?
Clarify the operations you want to perform on LogicComponent. Assuming you are trying to achieve something like this:
Step 1: Add a new entry to the map:
LogicComponent comp;
EntityID id = 99;
UnitInfos info = comp.add(id);
Step 2: Initialize the info:
info.x = 10.0;
info.y = 11.0
// etc
Step 3: Get the info object again:
UnitInfos info2 = comp.instances[id]; // this is uninitialized.
Then, a few code comments are in order:
The info object returned by comp.add is a COPY of the object you added to the map. By modifying it, you are not modifying what is in the map.
The simplest fix is to create a map of pointers to the object instead of the object itself.
typedef std::map<EntityID, T*> pinstances_map;
T * add(EntityID id) {
T* t = new T();
instances[id] = t;
return t;
};
// initialize as
UnitInfo *info = comp.add(id);
info->x = 10.0;
info->y = 11.0;
// retrieve as
UnitInfos *info = comp.instances[id];
Also, do use an accessor method to get the mapped value, instead of exposing the map object as public. Make the instances variable protected, and add a public get() method.
Edit: This code works fine for me:
#include <map>
#include <iostream>
using namespace std;
template<typename T>
class Component
{
public:
typedef map<long, T*> pinstances_map;
pinstances_map instances;
T * add(long id)
{
T *t = new T();
instances[id] = t;
return t;
}
};
struct UnitInfo
{
float x, y;
};
class LogicComponent: public Component<UnitInfo> {};
int main()
{
LogicComponent comp;
UnitInfo *info = comp.add(99);
info->x = 10.0;
info->y = 11.0;
UnitInfo *info2 = comp.instances[99];
cout << info2->x << " " << info2->y;
return 0;
}
might be that
T add(EntityID id) {
T* t = new T();
instances[id] = *t;
return *t; // return value and map instance are not the same anymore
};
should be
T& add(EntityID id) {
instances[id] = T();
return instances[id];
};
It sounds like you defined your indexing operator as:
template <typename T>
T& Component::operator[]( EntityID id )
{
return instances[id];
}
Or something like that.
The likely unexpected effect of this is that it will automatically insert default-constructed instance of T into the map and then return it for non-exising entries. This is done in std::map so natural assignment syntax like instances[10] = t; works.
The key point here is constness. Define it exactly as above except returning by value and with a const attribute:
template <typename T>
T Component::operator[]( EntityID id ) const
{
return instances[id];
}
This way you will get an exception when you try retrieving by non-existing key. Better yet, just typedef it like bellow and be done with it:
typedef std::map<EntityID,UnitInfos> EntityUnitMap;
Others already mentioned that you don't need to dynamically allocate an object - you store a copy in the container anyway - and that you leak memory when you do that.