Sky
Sky

Reputation: 429

C++: Keeping track of class objects

I defined a C++ class and numerous objects of that class get created during my program's runtime.

I need a get_object_by_name method.

This is what I would do in python:

class Person():
    all_instances = []
    def __init__(self, name, age):
        self.name = name
        self.age = age 
        self.all_instances.append(self)
    @classmethod
    def get_obj_by_name(cls, name):
        for obj in cls.all_instances:
            if obj.name == name:
                return obj 

How can I do that in c++?

Upvotes: 4

Views: 1224

Answers (3)

schorsch312
schorsch312

Reputation: 5698

C++ has no such feature as all_instances in python.

You have to manage this yourself.

First you need to store your different objects of the class in a container (e.g. std::list<Person> persons.

The get_object_by_name would look like this

Person & get_object_by_name(const std::string &name) {
    for (auto & person : persons) {
        if (person.get_name() == name) {
            return person;
        }
    }
}

Person needs to have a method get_name(). Alternatively, you can overload the operator ==. get_object_by_name needs access to persons. Thus it is a good idea to put them into a class

class Persons{
  public:
    Person & get_object_by_name(const std::string &name);
    // constructor to fill persons
    // method to fill persons

  private:
    std::list<Person> persons;
};

As SPD pointed out the choice of the container is not trivial. If you would use and std::vector and it grows over time it will cause a re-allocation and thus all references returned are invalidated.

Upvotes: 1

pptaszni
pptaszni

Reputation: 8217

As mentioned in the other answers, this kind of feature does not exist in C++ and you have to code it yourself. You already have some examples of how to achieve it by defining external storage class and keep track of the objects by yourself. Below is another approach that delegates the responsibility of objects tracking to the Person class itself. Additional assumptions:

  • you can only use parametric constructor (or remove explicit keyword to make it converting constructor)
  • names of Persons must be unique
  • you will be careful and you will not try to call delete on objects allocated on the stack
class Person
{
public:
  /* for example simplifity I delete all the other CTors */
  Person() = delete;
  Person(const Person& other) = delete;
  Person(Person&& other) = delete;
  explicit Person(const std::string& name): name_(name)
  {
    std::cout << "Person(" << name_ << ");" << std::endl;
    if (all_instances_.count(name_) != 0)
    {
      std::cout << "Person with name " << name_ << " already exists" << std::endl;
      throw std::runtime_error("Person with that name already exists");
    }
    all_instances_.emplace(std::make_pair(name_, this));
  }
  ~Person()
  {
    std::cout << "~Person(" << name_ << ");" << std::endl;
    all_instances_.erase(name_);
  }
  static Person* get_person_by_name(const std::string& name)
  {
    if (all_instances_.count(name) == 0)
    {
      std::cout << "Person with name " << name << " does not exist" << std::endl;
      return nullptr;
    }
    return all_instances_.find(name)->second;
  }

private:
  static std::map<std::string, Person*> all_instances_;
  std::string name_;
};

std::map<std::string, Person*> Person::all_instances_;


int main(int argc, char* argv[])
{
  Person p1("person1");
  try
  {
    Person p2("person1");  // exception
  }
  catch (const std::exception& e)
  {
    std::cout << "Exception: " << e.what() << std::endl;
  }
  {
    Person p3("person3");
    Person* p4 = new Person("person4");
    new Person("person5");  // lost pointer, but later we get it from the map
    delete p4;
    // p3 out of scope
  }
  auto person5 = Person::get_person_by_name("person5");
  delete person5;
  auto person1 = Person::get_person_by_name("person1");
  if (person1) std::cout << "Person1 still exists" << std::endl;
  auto person3 = Person::get_person_by_name("person3");
  if (!person3) std::cout << "Person3 does not exist anymore" << std::endl;
  return 0;
  // p1 out of scope
}

Expected output:

Person(person1);
Person(person1);
Person with name person1 already exists
Exception: Person with that name already exists
Person(person3);
Person(person4);
Person(person5);
~Person(person4);
~Person(person3);
~Person(person5);
Person1 still exists
Person with name person3 does not exist
Person3 does not exist anymore
~Person(person1);

Example verified with valgrind.

Upvotes: 0

PooSH
PooSH

Reputation: 701

You can use either unordered_map (hash map, requires C++11) or map (rbtree) to simulate Python's dictionaries.

class Object {
public: // for simplicity. You will want ctor, getters and setters instead.
    std::string name;
    // other fields...
};

std::unordered_map<string, Object*> objects;

Object * get_obj_by_name(const std::string &name) {
    auto map_iterator = objects.find(name);
    return map_iterator == objects.end() ? nullptr : map_iterator->second;
}

Keep in mind that there is no automatic memory management in C++, so you need to store your objects somewhere to prevent memory leaks or dangling pointers. If you want objects to own the objects, then replace raw pointer with unique_ptr (or shared_ptr depending on use-cases):

std::unordered_map<string, std::unique_ptr<Object>> objects;

Upvotes: 1

Related Questions