Storing struct instances in a std::map - c++

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.

Related

Boost::variant pointer to type not yet set

Suppose I had a boost variant instance containing a default initialised value (DataContainer). After a certain point in the program, it is guaranteed that the variant instance will contain a certain type (float) before a critical part of the program, but I only have access to the variant instance prior to it being set to this guaranteed type (DataDispatcher::set).
How can I create a float pointer to the location where the variant instance will be stored? Is it possible? My current attempt in Dispatcher::set causes a runtime error since the variant does not yet contain the float type.
Thanks
using Variant = boost::variant<std::string, float>;
struct DataContainer {
Variant value; // default initalised
};
class DataDispatcher {
public:
void set(DataContainer* dc) {
// this causes a fpointer to be equal to nullptr because dc->value is a string
// how to fix this?
fpointer = boost::get<float>(&dc->value);
}
// critical part of the program
void dispatch() {
importantCalculation(*val + 5);
}
private:
float* fpointer;
};
int main() {
DataContainer dc;
DataDispatcher dd;
dd.set(&dc); // only have access to variant instance here
dc.val = 1.4252; // guarantee it will contain a float
dd.dispatch(); // critical part
}
How can I create a float pointer to the location where the variant instance will be stored? Is it possible?
That's not reliably possible. It smells like a design issue when you "need" to store a pointer to data before it exists.
The natural solution is to store a reference to the variant instead: Live On Coliru
If you are convinced that the "optimization" of keeping a direct pointer to the float value is significant, you could do that manually:
Also Live On Coliru
#include <boost/variant.hpp>
#include <iostream>
#include <string>
void importantCalculation(float v) {
std::cout << "importantCalculation(" << v << ")\n";
}
using Variant = boost::variant<std::string, float>;
struct DataContainer {
Variant value;
};
class DataDispatcher {
public:
void reset() {
_cached = nullptr;
_ref = nullptr;
}
void set(DataContainer const& dc) {
_ref = &dc;
update_cache(); // in case it is already float
}
// critical part of the program
void dispatch() { importantCalculation(get() + 5); }
private:
DataContainer const* _ref = nullptr;
float const* _cached = nullptr;
void update_cache() {
_cached = nullptr;
if (_ref)
if (auto* fp = boost::get<float>(&_ref->value))
_cached = fp;
}
float get() {
if (!_cached)
update_cache();
assert(_cached);
return *_cached;
}
};
int main() {
DataContainer dc;
DataDispatcher dd;
dd.set(dc); // only have access to variant instance here
dc.value = 1.4252; // guarantee it will contain a float
dd.dispatch(); // critical part
// important:
dd.reset();
dc.value = "3.6474";
dc.value = 2.5363;
dd.set(dc); // important
dd.dispatch();
}
Prints
importantCalculation(6.4252)
importantCalculation(7.5363)
Note that other variant implementations might document a guaranteed element storage layout for small element types, and you could then rely on that documented guarantee.

How to "reassign" a shared_ptr<base> to a new derived object

I'm writing an interpretive language using C++. Now the problem is I want to implement features like reassignment:
VAR a = [1,"2",[3,4]]
VAR a[0] = 100
In my language, a List is a vector of shared_ptr<Data>, so that you can store different types of data in one list. If someone wants to change an element, I can get the shared_ptr<Data> elem, and the shared_ptr<Data> value that's about to be assigned.
Something like:
shared_ptr<Data> elem = visit(elem_node)
shared_ptr<Data> value = visit(value_node)
*elem = *value;
Sorry, I forgot to say that visit() returns a value, not a reference, that's why I didn't just let elem=value;
It turns out the only thing that has been changed is the data member from the Data class.
But what I want is to let the shared_ptr "repoint" to a new object. Is it possible?
I've tried dynamic_pointer_cast, if the origin element and the new value are of the same type, everything is fine. But as I pointed out, I allow different types of elements in one list, so this can only be my last straw.
I've written a demo code based on Remy Lebeau's answer(thanks):
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;
struct Data
{
virtual ~Data() = default;
virtual void print() = 0;
};
struct Integer : Data
{
int value;
Integer(int val) : value(val) {}
void print() override { cout << "integer(" << value << ")" << endl; }
};
struct String : Data
{
string value;
String(string val) : value(val) {}
void print() override { cout << "string(\"" << value << "\")" << endl; }
};
struct RuntimeResult
{
RuntimeResult success(const shared_ptr<Data> &value)
{
this->value = value;
return (*this);
}
shared_ptr<Data> registry(const RuntimeResult &res)
{
// this is for handling error
// but this simplify version simply return value;
return res.value;
}
shared_ptr<Data> value;
};
int main()
{
// simulate a List variable
vector<shared_ptr<Data>> list;
list.push_back(make_shared<Integer>(123456));
list.push_back(make_shared<String>("hello"));
RuntimeResult res;
// inside registry, it should be something like visit_IndexNode(elem_node)
// to interprete a node on AST Tree
// but the return is simply like what have shown below
shared_ptr<Data> elem = res.registry(RuntimeResult().success(list.at(0)));
// Using Visual Studio 2019's debugging mode
// we can see that elem's ptr == list[0]'s ptr
// in other word, they are pointing to the same thing(123456)
shared_ptr<Data> value = res.registry(RuntimeResult().success(make_shared<String>("test")));
elem = value;
elem->print(); // "test"
list[0]->print(); // still 123456
return 0;
}
this shows exactly my problem
But what I want is to let the shared_ptr "repoint" to a new object. Is it possible?
Yes. Simply assign one shared_ptr<Data> to another, no casting needed:
shared_ptr<Data> &elem = ...;
shared_ptr<Data> value = ...;
elem = value;
Online Demo
As Remy pointed out, my origin problem was due to a bad design. However, I've managed to get around it by using shared_ptr<unique_ptr<Base>>.
The idea was to somehow change all reference of the shared_ptr to "re-point" to a new derived object. Following that idea, I've found a Q&A Replace all references to a object in a shared_ptr
By adding the second layer of indirection, I can do things like:
class Data;
class Integer:public Data;
class String:public Data;
shared_ptr<unique_ptr<Data>> origin_value = make_shared<unique_ptr<Data>>(make_unique<Integer>(123));
auto copy = origin_value;
shared_ptr<unique_ptr<Data>> new_value = make_shared<unique_ptr<Data>>(make_unique<String>("hello"));
(*copy).reset((*new_value).release());
This will change both origin_value and copy to new_value.

How to register an object using string or int as key dynamically, and store, access it with the key in C++?

I want to design a data dashboard framework with c++ with the following requirements:
clients can register data type to the dashboard using a key (string or integer are both acceptable)
clients can store/access the object with the known data type and key
The following example code demonstrates how I want to use it
// data.hpp
struct DataA
{
DataA();
int a;
};
struct DataF
{
DataF();
float f;
};
// data.cpp
static DataA s_da;
static DataF s_df;
DataA::DataA()
{
if(!Dashboard::Register("A", s_da)) {
cerr << "unable to register DataA, key is used";
}
}
DataF::DataF()
{
if(!Dashboard::Register("F", s_df)) {
cerr << "unable to register DataF, key is used";
}
}
// main.cpp
int main ()
{
DataA da;
da.a = 123;
Dashboard::Set("A", da);
DataF df;
df.f = 3.14;
Dashboard::Set("F", df);
cout << ((DataA)Dashboard::Get("A")).a << endl; // 123
cout << ((DataF)Dashboard::Get("F")).f << endl; // 3.14
return 0;
}
However, I can't come up with any idea to implement the Dashboard class to provide the interface.
How could I dynamically register an object with a given datatype and key? Is there any design pattern that addresses this requirement?
I built something like you are describing on my team as an internal for a library that was used for routing messages across the application.
If you are stuck on C++11 (such that std::any or std::variant is unavailable), you could have an empty virtual base class type as follows:
class BaseValue
{
public:
BaseValue() {};
virtual ~BaseValue() {}; // need one virtual method for dynamic_cast
};
And a derived template class such as the following:
template <typename T>
class Value : public BaseValue
{
public:
Value(const T& t) : _t(t)
{}
T _t;
};
Then your data structure is this. It's a map between string to BaseValue pointers
unordered_map<string, BaseValue*> dashboard;
We'll smuggle data into the map above by using the template class Value that derives from BaseValue.
Inserting into the the dashboard is like this:
template <typename T>
void insert(const string& name, const T& t)
{
Value<T>* pT = new Value<T>(t);
dashboard.insert(name, pT);
}
Fetching is something like this. There's different ways to structure a "get" call with respect to "not found" scenarios.
template<typename T>
T& get(const string& name, const T& default = {})
{
auto itor = dashboard.find(name);
if (itor != dashboard.end())
{
BaseValue* pBaseValue = itor->second;
T* pValue = dynamic_cast<T*>(pBaseValue);
if (pValue)
{
return pValue->_t;
}
}
return default; // you could also throw an exception
}
Example insert:
DataA da;
da.a = 123;
insert("a", da);
Example fetch:
DataA& da = get<A>("a");
There's a lot of improvements you can make to the above. For starters, you can convert all of helper functions into a class. Use shared_ptr instead of raw pointers internally. etc...

Knowing when to delete associated user data from a std::map<void *, ...>

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;
}

How can I create a switch for class members?

Say I have a class with a couple of data members, and I want a class method that returns one, and the next time it is called returns the value of the other. Something like:
class MyClass
{
public:
MyClass():switch(0){};
int get();
private:
int intA, intB;
int sw;
};
int MyClass::get()
{
if ( (++sw)%2 )
return intA;
else
return intB;
}
What would a more elegant way of doing it be? I don't like the if...else statement very much. It's fine for something like return, but if I'm actually using more complex operations, I end up duplicating a ton of code. Or having to create a second method within each method that is called after I resolve what element I'm pointing to.
What I'd prefer to do, ideally, is to use some form of pointer, so I can do
class MyClass
{
public:
MyClass():switch(&intA){};
int get();
void toggleSwitch();
private:
int intA, intB;
int * sw;
};
int MyClass::get()
{
return *sw;
}
void MyClass::toggleSwitch()
{
if ( sw == &intA )
sw = &intB;
else
sw = &intA;
}
Or something to that effect. I could call toggleSwitch(), and have my class operate on either one or the other value easily.
I still don't like it though. I prefer to avoid if's when possible, and I shouldn't need one in this case. This use of a naked pointer should be pretty safe, but I was thinking I could have something like std::unique_ptr holding each element and then std::swap them. But then the pointers would own the elements, and they'd be dynamic memory instead.
So is there a better way to do it?
Well, switch is a keyword, but I'll roll with it. How about an array of pointers?
int *fields[] = {&intA, &intB};
int MyClass::get()
{
return *fields[++switch % 2];
}
This would expand nicely if you could have additional variables later.
Or maybe:
int MyClass::get()
{
return *fields[switch = 1 - switch];
}
If you return a reference then you could use get() internally.
int &MyClass::get()
{
return *fields[switch = 1 - switch];
}
I would encapsulate the concept of a toggling value:
template<typename T>
class Toggleable {
T first;
T second;
T* current;
T* other;
public:
Toggleable(const T& first, const T& second)
: first(first),
second(second),
current(&first),
other(&second) {
}
bool toggle() {
std::swap(current, other);
}
const T& get() const {
return *current;
}
}
Then use as:
class MyClass
{
Toggleable<int> value;
public:
MyClass()
: value(42, 1729)
{
}
const int& get() {
value.toggle();
return value.get();
}
};