Home > Back-end >  C Use the class type as the value of the map
C Use the class type as the value of the map

Time:01-05

class Animal {
 public:
  virtual std::string Say() = 0;
};

class Mouse : public Animal {
 public:
  std::string Say() { return "I am a mouse."; }
};

class Duck : public Animal {
 public:
  std::string Say() { return "I am a duck."; }
};

class Dog : public Animal {
 public:
  std::string Say() { return "I am a dog."; }
};

class Zoo {
 public:
  void Build(std::map<std::string, ClassType> animals) {
    for (std::map<std::string, ClassType>::iterator it = animals.begin(); it != animals.end(); it  ) {
      this->animals[it->first] = new it->second();
    }
  }

 private:
  std::map<std::string, Animal*> animals;
};

int main() {
  Zoo zoo;
  zoo.Build({{"Mickey", Mouse}, {"Daisy", Duck}, {"Goofy", Dog}});

  return 0;
}

It is hoped that the class type can be passed in as the value of the map when calling zoo.Build().

And use this class type inside Build, ex: new Class();

Thanks a lot!

CodePudding user response:

C classes are types, and you can't pass types as arguments or create "type" objects. You need to use some kind of builder pattern to solve your problem.

For example you could could have a (fluent) builder-class like this:

class ZooBuilder;  // Forward declaration

class Zoo
{
    friend ZooBuilder;

public:
    // Public interface of the Zoo class...

private:
    std::unordered_map<std::string, std::unique_ptr<Animal>> animals_;
};


class ZooBuilder
{
public:
    ZooBuilder(Zoo& zoo)
        : zoo_{ zoo }
    {
    }

    template<typename animalT>
    ZooBuilder& add(std::string const& name)
    {
        zoo_.animals_[name] = std::make_unique<animalT>();
        return *this;
    }

private:
    Zoo& zoo_;
};

You can then use it like:

Zoo my_zoo;

ZooBuilder(my_zoo)
    .add<Mouse>("Mickey")
    .add<Duck>("Daisy")
    .add<Dog>("Goofy");

In my example I have created a separate builder class, but you can put the add function template into the Zoo class itself, to make the Zoo class its own builder.

CodePudding user response:

You can't pass class types around. You need to create instances of something. That something could be an instance of a class template, where the template parameter is the type of animal you'd like to instantiate:

template<class T>
struct am {};     // animal maker

In this, you could also store the arguments you need (if any) to instantiate the correct animal and also the Key value that you need in the map. You can also add an implicit conversion from such an instance to the map's value_type (std::pair<const Key, T>).

template<class T>
struct am {
    std::string_view name;

    // implicit conversion to the map's value_type:
    operator auto () && {
        return std::pair<const std::string, std::unique_ptr<Animal>>{
            name, std::make_unique<T>()};
    }
};

Your Zoo could then be:

class Zoo {
public:
    template<class... Args>
    void Build(Args&&... args) {
        // fold over the comma operator:
        (..., animals.emplace(std::forward<Args>(args)));
    }

private:
    std::map<std::string, std::unique_ptr<Animal>> animals;
};

and used like so:

Zoo zoo;
zoo.Build(am<Mouse>{"Mickey"}, am<Duck>{"Daisy"}, am<Dog>{"Goofy"});

Note: Your base class, Animal, needs a virtual destructor to be able to delete animals via the base class pointers:

class Animal {
public:
    virtual ~Animal() = default;   // add this
    virtual std::string Say() = 0;
};

Demo

  • Related