简体   繁体   中英

c++ Mapping class to number

I recently started with c++ development. I've come to a problem of which I am not able to solve, given that I am unaware if the following is possible.

I want to create a mapping between a number and class, which are derived from an abstract class.

Essentially what I would like to be able to do is create a factory method that can create a new instance of a class based on a given number associated with that class.

I know that I could do the following...

Vehicle *Vehicle::from_type(byte type)
{
    switch(type)
    {
        case 0x00: return new Bicyle();
        case 0x01: return new Car();
        ...
        case 0x10: return new Truck();
    }

    return null;
}

..., but I'd rather not as I want to keep it DRY.

It there a way where one can do something along the lines of this:

// I know this is incorrect syntax
const map<byte, class extends Vehicle> VEHICLE_MAPPING = {{0x00, Bicyle}, {0x01, Car}, ..., {0x10, Truck}};

Vehicle *Vehicle::from_type(byte type)
{   
    return new VEHICLE_MAPPING[type]();
}

I can see how your approach could work with usage of std::map<uint8_t, std::unique_ptr<Vehicle>> , but there is a problem - you wouldn't be able to initialise that map with initializer_list , since it copies the elements and, as we all know, std::unique_ptr cannot be copied. You would have to create an init() function to initialise the map that would use similar logic to your Vehicle *Vehicle::from_type(byte type) , which would simply be pointless given you already have your function.

Furthermore, I disagree that your first solution violates DRY. It is actually correct in a sense that you won't be forced to use switch or if s elsewhere in the code. I'd definitely stick with it.

The final note - you could use std::map<uint8_t, std::shared_ptr<Vehicle>> instead of std::map<uint8_t, std::unique_ptr<Vehicle>> and initialise it with initializer_list , since std::shared_ptr can be copied, but I wouldn't advise that since it wrongly indicates the usage of shared_ptr . If you somehow feel forced to do so, here is an example:

class Base{ public: virtual ~Base() = default; };
class Derived1 : public Base{};
class Derived2 : public Base{};

class derived_factory{
    private:
        derived_factory();
        static inline std::map<uint8_t, std::shared_ptr<Base>> base_map = {
            {0x00, std::make_shared<Derived1>()},
            {0x01, std::make_shared<Derived2>()}
        };
    public:
        static std::unique_ptr<Base> from_type(uint8_t type)
        {
            return std::make_unique<Base>(*base_map[type]);
        }
};

int main()
{
    auto ptr = derived_factory::from_type(0x00);
    // ptr is of a type std::unique_ptr<Base> and points to Derived1 object
}

Additional note that should be a final discouragement of using this solution is that it's quite slow. It constructs the objects in a map and does nothing with them except for keeping them as 'templated' copy examples.

If they're all derived from a base class, you can use the factory pattern, eg, from Loki's implementation (see Modern C++ Design for the details, though that book is pre-C++11).

The following creates some concrete vehicles and puts them in a vector and then calls the drive() method on each of them:

#include <iostream>
#include <memory>
#include <vector>
#include "factory.h"

struct Vehicle
{
  virtual ~Vehicle() = default;
  virtual void drive() = 0;
};

struct Car : Vehicle
{
  static constexpr auto ID = 1;
  void drive() override { std::cout << "Car\n"; }
};

struct Truck : Vehicle
{
  static constexpr auto ID = 2;
  void drive() override { std::cout << "Truck\n"; }
};

// Create the factory object
auto g_factory = MyUtil::Factory<std::unique_ptr<Vehicle>, int>{};

void RegisterTypesWithFactory()
{
    // We pass in creator functions for each type. Note that these
    // could be lambdas or some other freestanding function and they
    // could accept parameters.
    g_factory.Register( Car::ID,   &std::make_unique<Car> );
    g_factory.Register( Truck::ID, &std::make_unique<Truck> );
}

int main()
{
    // Configure the factory
    // Note: Registration can be done any time, e.g., later based on input 
    // from a file. I do them all at once here for convenience of illustration.
    RegisterTypesWithFactory();

    // Create some objects with the factory
    auto vehicles = std::vector<std::unique_ptr<Vehicle>>{};
    vehicles.emplace_back( g_factory.Create( Car::ID ) );
    vehicles.emplace_back( g_factory.Create( Truck::ID ) );

    // Do something with the objects
    for( const auto& v : vehicles )
    {
        v->drive();
    }
}

Which prints:

Car
Truck

See it run live on Wandbox .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM