class convoy_t {
private:
uint ID;
/* some other fields too */
public:
int get_ID() { return ID; }
/* plus some other methods, like a constructor */
};
If I have a pointer convoy_t *ptr
to an instance, I can get the identifier for that instance with ptr->get_ID()
.
If I have the value of a member variable (identifier
) for an instance, how I can get a pointer to that instance?
I considered looping through all the instances of the class to check whether ptr->get_ID() == desired_ID
, but I haven't seen code like that in the tutorials I've read, and I don't know how to get all the instances of a class.
How to find an instance of a class with specific member values in a std::vector assumes I already have a std::vector
containing the instances. I don't.
Tutorials on how to access the data members of an instance, like Instance Variables in C++ Programming, don't talk about getting the instance if I only know the data.
I am hoping to improve the debugging facilities in a multiplayer game
(Simutrans Extended).
Convoys (buses, trains, etc.) are instances of a class
convoy_t
. They have a member variable uint ID
. Sometimes clients
become desynchronized, and at that point, I can get the ID of the convoy
that caused the problem. I need to find the convoy_t
object so I can
then pass it to various other functions to get more information.
But, at the point the desync is detected, I don't have a container of instances readily available. The game engine might have one somewhere I could use for this purpose, but if there's a standard way to do this then that would seem better.
There is no way, in general, to find a C++ class instance by knowing the value of one of its data members, for two reasons:
The data member value may not be unique.
Even if it is, there is no facility provided by the language or standard library for searching among all instances of a class.
The most typical way of solving this problem is to build some sort of container in advance that has all of the instances you want to search through.
For example, if there is an existing function that creates objects:
convoy_t *makeConvoy(...)
{
convoy_t *convoy = new convoy_t(...);
// maybe do other things
return convoy;
}
then you can modify this code to keep track of all of the objects by
first creating a container, for example a
std::map
. For
simplicity we'll make it at file scope, but there may be a better place
to put it in the real program:
#include <map> // std::map
#include <utility> // std::make_pair
// Map from convoy ID to a pointer to the instance.
std::map< int, convoy_t* > convoyMap;
Then, in makeConvoy
, before the return
, add code that inserts it
into the map:
// This uses the "traditional" insertion syntax. See notes below.
convoyMap.insert(std::make_pair(
convoy->getID(), // key
convoy)); // value
Finally, when you have an ID and want its convoy, look up the ID in the
map using the at
method:
convoy_t *convoy = convoyMap.at(ID);
Some notes:
You need to ensure IDs are unique. If they aren't, then when you try
to add the second object with the same ID into the map, the
insert
call will not do anything (see linked reference for details).
The at
method will throw
std::out_of_range
if the ID is not found. You should write an exception handler to deal
with that possibility.
The call to insert
can also be written using a more compact
notation that takes advantage of initializer lists, introduced with
C++11: convoyMap.insert({convoy->getID(), convoy});
.
If an object is deleted, then it must also be removed from the map. Otherwise, the pointer in the map will "dangle", meaning it points at memory that will subsequently be reused for another purpose, leading to all sorts of problems.
If the ID of an object changes, then you have to remove it from the map and re-insert it with the new ID. The map itself has no knowledge that the ID used as a key is meant to be the same as the data member value.
Here is a complete program demonstrating the above technique:
// convoy.cc
// Demonstrate finding objects by ID.
#include <cassert> // assert
#include <map> // std::map
#include <utility> // std::make_pair
int nextConvoyID = 1;
class convoy_t {
public: // instance data
int m_id;
int m_otherData;
public: // methods
convoy_t(int id, int otherData)
: m_id(id), m_otherData(otherData) {}
int getID() const { return m_id; }
};
// Map from convoy ID to a pointer to the instance.
std::map< int, convoy_t* > convoyMap;
convoy_t *makeConvoy(int otherData)
{
int id = nextConvoyID++;
convoy_t *convoy = new convoy_t(id, otherData);
if (false) {
// Traditional syntax.
convoyMap.insert(std::make_pair(
convoy->getID(), // key
convoy)); // value
}
else {
// More compact notation using an initializer list.
convoyMap.insert({convoy->getID(), convoy});
}
return convoy;
}
int main()
{
// Make some convoys.
convoy_t *c1 = makeConvoy(11);
convoy_t *c2 = makeConvoy(12);
convoy_t *c3 = makeConvoy(13);
// Get one of the IDs.
int id = c2->getID();
// Amnesia strikes! Where did 'id' come from?
convoy_t *convoy = convoyMap.at(id);
// Ah, there it is.
assert(convoy == c2);
return 0;
}
// EOF
You say you're working in Simutrans Extended. I don't know anything
about it, but as was noted in the comments, it seems to have a
world
object that can be
traversed:
// loop through all convoys
for (vector_tpl<convoihandle_t>::const_iterator i = world->convoys().begin(), end = world->convoys().end(); i != end; i++)
{
current_convoy = *i;
// only consider lineless convoys which support this compartment's goods catetory
if ( !current_convoy->get_line().is_bound() && current_convoy->get_goods_catg_index().is_contained(catg) )
{
temp_linkage.convoy = current_convoy;
linkages->append(temp_linkage);
transport_index_map[ 65536u + current_convoy.get_id() ] = linkages->get_count();
}
}
Looping through all convoys in order to find one with a particular ID is not very efficient, but it may suffice for what you're doing. There might also be a map of conveys already existing; like I say, I don't know that program.