Noob at C++: how to do an associative array of object => value?
I'm trying to create a global (singleton) class that can associate any type of object with an integer value. I was thinking of using a map<T*, int>
variable, but was wondering if there was any other way to do it.
UPDATE: Here is a prototype of my class. Let me know what I'm doing wrong ! It compiles and work fine, but I'm not sure if it could be done in a better way. For instance, int addr = (int)&objectA;
looks pretty ugly.
UPDATE 2: I've chosen to go the inheritance way as proposed by Potatoswatter. That will be much easier considering what I'm trying to achieve. Thanks everyone for the feedback !
#include <iostream>
#include <map>
#include <string>
using namespace std;
struct PortPin {
int port;
开发者_JAVA技巧 int pin;
};
class Carte {
public:
void associate(int object_address, int port, int pins);
map<int, PortPin> map_;
};
void Carte::associate(int object_address, int port, int pins) {
PortPin values = {port, pins};
map_[object_address] = values;
}
class A {
public:
A() {}
};
class B {
public:
B() {}
};
void main() {
Carte carte;
A objectA;
B objectB;
int addr = (int)&objectA;
carte.associate(addr, 2, 7);
cout << "Port: " << carte.map_[addr].port
<< " Pin: " << carte.map_[addr].pin;
}
You should use a base class.
struct PortPin {
int port;
int pin;
bool is_valid;
PortPin() { is_valid = false; }
void associate( int in_port, int in_pin ) {
port = in_port;
pin = in_pin;
is_valid = true;
}
};
class A : public PortPin {
public:
A() {}
};
class B : public PortPin {
public:
B() {}
};
…
A blah;
A.associate(2,7);
The kind of pointer arithmetic you're trying to do isn't treading water, it's treading fiery acid.
It's not really that important to be able to associate any object. Were you going to associate your singleton with a PortPin? Always derive every class from an appropriate base to acquire needed services.
If you're concerned about the memory (a dozen or two bytes at most) taken by the PortPin base, you can create an empty base class to give you a T*
type with which to key your map
. However, consider that each map
entry takes three pointers plus the data, hence being at least twice as big as the PortPin struct itself. And it will leak unless you're careful, and that will cause mysterious bugs when addresses are reused. (This problem would be all but impossible to eliminate if you really can't constrain the key classes.) Finally, the map is much slower and more tedious than just accessing a field.
If you do not require a specific value, you can simply cast the object's address to (unsigned long) or other suitable integer type (intptr_t would be optimal, but it is a C99 feature that is not available in standard C++).
Notice that the above associates every instance of an object with a different value. If you want types, as you say, use typeid(T), which gives you an unique object for each type (the objects can be compared to other such objects).
Well, if you really mean associating "any object" rather than "any object value", then pointers sound like they'll do what you want. They probably won't work for things like integer constants or the like, though, which have a value without being held in an allocated object. And, of course, any time that something gets copy-constructed (say you pass it by value in a function call), that will create a new object, and so it will get a different tag in your map.
Also, as mentioned in Potatoswatter's answer, addresses do get reused. Obviously they can't get reused while the object is still in existence, but you could get some weirdness this way if you're not careful; you'll have erroneous entries for objects that you haven't yet added to the map. So don't try to look up the map values until you add them, and don't try to look up objects that have been destroyed!
With that said, assuming that really does work for your use-case (and Potatoswatter's idea of putting the port and pin values in the objects themselves isn't a better one), then you don't want to convert all the pointers to ints; aside from the size issues, that's conceptually a bit convoluted, which is why the casts are ugly. You don't really care about the integer values, just about having the pointers all converted to a common format that can be compared -- so, convert all the T* pointers for the various types to void pointers, which are specifically intended for that purpose, and put them in a map<void*, PortPin>
map.
This will also have a difficulty that an object and its first component (and that component's first component and so forth) will all have the same pointer, and thus will all be associated with the same lookup value in your map. If you care, you could construct a struct containing both the pointer and a type identifier (a std::type_info
to contain the typeid()
of the object, or an int
for its sizeof()
if you're compiling without RTTI on and thus typeid()
isn't available), and use that as the tag for the map.
精彩评论