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.