-4

Suppose I have a class like this:

class convoy_t {
private:
  uint ID;

  /* some other fields too */

public:
  int get_ID() { return ID; }

  /* plus some other methods, like a constructor */
};

I know that if I have a pointer convoy_t *ptr to the instance, I can get the identifier for that instance with ptr->get_ID(). But how do I do the reverse? Is this even possible?

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. My understanding is a class is a set of instances, but I can't find how to access that set.

When I searched for how to do this, I found How to find an instance of a class with specific member values in a std::vector, but that assumes I already have a std::vector containing the instances. What if I don't?

I also found many tutorials on how to access the data members of an instance, like Instance Variables in C++ Programming, but none of them talk about getting the instance if I only know the data.

Additional details on why I want to do this

I am hoping to improve the debugging facilities in a multiplayer game (Simutrans Extended). In the game, 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.

Scott McPeak
  • 8,803
  • 2
  • 40
  • 79
Matthew
  • 156
  • 1
  • 9
  • 6
    ***and in this situation I don't have a pointer*** Why don't you have a pointer? These instances should be stored in some kind of container. – drescherjm Aug 17 '23 at 20:48
  • 4
    Can't you keep all convoys in a `std::map`/`std::unordered_map`? – Ted Lyngmo Aug 17 '23 at 20:48
  • 6
    Not much you can to other than search through a list of the instances until you find the one that matches the value. If you don't have such a list, you're SOL. If many instances match the value, you'll have to filter the results further with more criteria. – user4581301 Aug 17 '23 at 20:50
  • 2
    In general you can't work directly backwards from a member variable to its object, because for `foo->int1`, `int1` is just an int, it has no member functions of `foo`, no `this`, it is not a linked list type struct with a pointer to its parent -- it is just an int, and the only address you have is the int itself. the exception is something like `foo->childobject` where `childobject` is itself a class or struct, and it was initialized to include a pointer member variable that points back to `foo` – Dave S Aug 17 '23 at 20:51
  • 3
    Side note: Learning C++ by hacking an existing game is going to be a tough road to C++ mastery. I recommend mastering the ins-and out of the language with a [good beginners book](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) (get an intermediate book if you already know other languages). Learn CPP looks well-organized, but I haven't personally worked through it to vet it's fitness for purpose. The two recommended beginners books, on the other hand are good, just a bit out of date. – user4581301 Aug 17 '23 at 20:57
  • Bad checksum algorithm. There are many factors that be added together to get 12. I had to fix a defect based on this premise. – Thomas Matthews Aug 17 '23 at 21:05
  • @user4581301: It's the other way around: I am learning C++ in order to improve the game :-) What puzzled me is that I thought a class *was* a list of the instances, but I have learned from your answer and others that is it not. Thank you. – Matthew Aug 17 '23 at 21:07
  • @drescherjm: Thank you. Your comment was helpful: I thought a class *was* some kind of container, but your comment taught me that I misunderstood the concept. I have edited the question in the light of that. – Matthew Aug 17 '23 at 21:08
  • 1
    @DaveS: I thought that the language would provide some way to iterate through the objects of a class, but I have learned from the comments that it doesn't, and there must be a separate structure to hold pointers. Now I know what to look for, I have found it and edited the question accordingly. (https://github.com/jamespetts/simutrans-extended/blob/master/tpl/quickstone_tpl.h) – Matthew Aug 17 '23 at 21:16
  • 3
    @Matthew I've spent all of 5 minutes looking at this, but it appears there is a `world` object that has a list of all convoys. See for example (in the code you linked) `path_explorer.cc` line 884. – Chad Aug 17 '23 at 21:22
  • 4
    My concern is looking at a large and complicated program like a game and learning snippets of the language in a disorganized manner in order to understand it will result in more frustration and wasted time than necessary. If you spent a week going through a good C++ text and picking up the basic fundamentals, I'm betting you'd be able to "eat" the game code more efficiently. Sure, it takes years of practice to be good at C++, but you don't need that much C++ to be able to reduce the amount of time lost to bad guesses to a manageable level. – user4581301 Aug 17 '23 at 21:49
  • 1
    You say : "I thought that..." this is a very inefficient way of learning C++, you really should understand at least the basics first and do some "toy" projects. Because you really cannot learn C++ by trial and error. – Pepijn Kramer Aug 18 '23 at 07:03
  • 1
    Good sources to learn cpp from are : A [recent C++ book](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) or have a go at https://www.learncpp.com/ (that's pretty decent, and pretty up-to-date). For C++ reference material use : [cppreference](https://en.cppreference.com/w/). And after you learned the C++ basics from those sources, look at the [C++ coreguidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines) regularely to keep up-to-date with the latest guidelines. – Pepijn Kramer Aug 18 '23 at 07:04
  • @Pepijn Kramer: I have been working systematically through LearnCPP for several months. But the game is bugged now :-) and there are several things I can do with really basic strings and existing functions once I get over this hurdle. – Matthew Aug 18 '23 at 08:00
  • 1
    Cool. Dont try to solve in game first. Write some tests first – Pepijn Kramer Aug 18 '23 at 09:09
  • 1
    The question has been closed as needing details or clarity. Perhaps the question may not have been clear to some users, but it was clear for Scott McPeak to provide an extremely helpful answer that addresses the key point: "there is no facility provided by the language or standard library for searching among all instances of a class". Therefore I would request that the question be re-opened so that other newbies can also learn from his answer, instead of being deterred by the closed tag. I will gladly edit to help that, but I'm not sure what needs editing. – Matthew Aug 19 '23 at 15:15
  • 2
    I think the question is reasonably clear, especially from its title. I also think that other people, especially those just starting out, may also have this question. – Scott McPeak Aug 19 '23 at 17:19
  • 2
    https://meta.stackoverflow.com/questions/426074/seemingly-reasonable-newbie-question-about-pointers-closed-as-needs-details-or – Scott McPeak Aug 19 '23 at 19:14
  • 1
    "it was clear for" They guessed. – philipxy Aug 20 '23 at 00:22
  • 5
    Related: *[Getting all objects of a class in C++](https://stackoverflow.com/questions/17509433/getting-all-objects-of-a-classes-in-c)* – Peter Mortensen Aug 20 '23 at 14:33
  • 2
    Although it is closely related, I don't consider the question to be a duplicate of [Getting all objects of a class in C++](https://stackoverflow.com/questions/17509433/getting-all-objects-of-a-class-in-c). Fundamentally, the question here is "how do I get a pointer from a data member value?" Explicitly iterating over all instances is one hypothetical way that could be answered (and the Q does now contemplate this), but a language could conceivably offer another, and a beginner who has this question might not realize that "first get all the instances" is the most plausible solution. – Scott McPeak Aug 21 '23 at 14:20
  • @Peter Mortensen: Although it isn't a duplicate, that post contains informative answers, so thank you. This question was not asked that way to avoid the XY problem. – Matthew Aug 21 '23 at 16:31

1 Answers1

3

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:

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

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.

Scott McPeak
  • 8,803
  • 2
  • 40
  • 79
  • 3
    Scott, this is a thorough and comprehensive answer that I will mark as accepted. The key point is in the second bullet point: "there is no facility provided by the language or standard library", but you kindly went beyond that and provided a complete explanation of how to properly handle this situation. – Matthew Aug 19 '23 at 15:09