c++classinstance

How do I access a particular instance of a C++ class when I only know the value of a member variable?


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.


Solution

  • 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 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.

    One approach: use std::map

    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:

    Complete example

    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
    

    There may already be a container you can use

    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.