Home > Back-end >  How to do unordered containers of inherited objects in C
How to do unordered containers of inherited objects in C

Time:12-16

The problem in question is related to the question and discussion found in this SO thread.

The problem is essentially as follows, I have an abstract class called players. Then I have two classes attackers and defenders. Now, I would like to have an unordered container (map, set, etc.) containing all players. For that I need a hash function (which is not the issue as both attackers and defenders have names) and an equality function. The latter is the problem. As discussed in the linked SO thread, it seems to be bad practice to inherit operator== and I can see why.

I now wonder what the idiomatic solution to my problem is. Is it to just have two containers? One for players and one for attackers? Are there other solutions?

CodePudding user response:

When you instantiate your map, you can specify a comparator:

template< class InputIt >
map( InputIt first, InputIt last,
    const Compare& comp = Compare(), /// << this here
    const Allocator& alloc = Allocator() );

Reference: https://en.cppreference.com/w/cpp/container/map/map

Then, you don't need to define the equality operator. In any case, in order to store polymorphic objects in a map, you'll need to store pointers.

So, you would need a comparator anyway, in order to compare the objects, not the pointer values.

CodePudding user response:

In order to store multiple different types in the same container, you have to take advantage of polymorphism.
Polymorphism requires you to use pointers to objects instead of actual objects. That's because C/C doesn't allow you to make arrays of multiple different types. The best way to do that in modern C is to use std::unique_ptr or std::shared_ptr from #include <memory>

To make a polymorphic container you'll need the following:

  • A common base struct/class that all of your types inherit from.
  • Some way to interface with subtype-specific methods/members.
    This can be a virtual method in the base object, a member of the base object, or you can also use typeid to figure out what type something is at runtime.
#include <iostream>
#include <vector>
#include <memory>
#include <typeinfo>

struct base {
  virtual ~base() = default;
  virtual int GetValue() const = 0;
};

struct A : base {
  int a;
  A(const int v) : a{ v } {}
  int GetValue() const override { return a; }

  void doSomething() const { std::cout << "Hello World!"; }
};
struct B : base {
  int b;
  int mult;
  
  B(const int v, const int mult) : b{ v }, mult{ mult } {}
  int GetValue() const override { return b * mult; }

  void doSomethingElse() const { std::cout << "!dlroW olleH"; }
};

int main()
{
  std::vector<std::unique_ptr<base>> vec;
  vec.emplace_back(std::make_unique<A>(5));    //< insert an object of type A
  vec.emplace_back(std::make_unique<B>(8, 2)); //< insert an object of type B

  for (const auto& it : vec) {
    std::cout << it->GetValue() << '\t';
    
    if (typeid(*it.get()) == typeid(A)) {
      ((A*)it.get())->doSomething(); 
    }
    else if (typeid(*it.get()) == typeid(B)) {
      ((B*)it.get())->doSomethingElse();
    }
    
    std::cout << std::endl;
  }
}

Outputs:

5   Hello World!
16  !dlroW olleH
  • Related