Nathan
Nathan

Reputation: 7709

Using C++ templates to create a class with custom components

Let me explain what I am asking for by an example. Imagine I have a class for a car. Now, the car may have a lot of extras:

I want to create the class with any combination of these options. Any of these options needs some data members. Imagine the class now looks like this:

class Car {
public:
  bool FourDoors;
  bool AutomaticDoorLocking;
  bool FourWheelDrive;

  Door doors[4];  //4 only needed if FourDoors=true
  DoorLockingElectronic doorElectronic; //Only needed if AutomaticDoorLocking=true
  TransmissionsShafts[4]; //4 only needed for FourWheelDrive=true

  void lockDoors() {
    if (AutomaticDoorLocking) {
      doorElectronic.lockDoors();
    } else {
      // Do manual door locking
    }
  }
};

So far so good, but now I want to create a lot of cars, so many that memory gets critical. And I do not need most of the extras in most of those cars. I could create a base class, and derive classes with those options enabled or disabled. But I would have to create 2^{#extras} classes to create all possible combinations, with a lot of double code.

So I thought maybe templates could be used? (that is the question).

I can imagine having a flag template, and rewrite the lockDoors like this:

template<int flags>
void Car<flags>::lockDoors() {
  if (flags | AutomicDoorLockingFlag) {
    doorElectronic.lockDoors();
  } else {
    // Do manual door locking
  }
}

Wonderful! But the class Car<0> still takes a lot of unnecessary space. So:

Can I somehow include or exclude class members depending on a template parameter?

Other Ideas how to deal with the situation are also welcome!

Upvotes: 2

Views: 3233

Answers (5)

Moo-Juice
Moo-Juice

Reputation: 38825

One possibility would be to introduce a feature class. The feature class would have some kind of a unique identifier (I've used int for the hell of it, but boost::uuids::uuid would be more preferable). It does nothing but define a feature of some sort:

class Feature
{
private:
    int m_nUniqueID;

protected:
    Feature(int _uniqueID) : m_nUniqueID(_uniqueID) {};
    virtual ~Feature(){};

public:
    const int& getUniqueID const {return(m_nUniqueID);};

}; // eo class Feature

From this, we can derive more concrete features:

class DoorsFeature : public Feature
{
private:
    int m_nDoors;

public:
    static const int UniqueId;
    DoorsFeature(int numDoors) : Feature(UniqueId), m_nDoors(numDoors){};
    virtual ~DoorsFeature(){};

    void lockDoors() { /* lock the doors */ };
}; // eo class DoorsFeature

class ABSFeature : public Feature
{
public:
    static const int UniqueId;
    ABSFeature() : Feature(UniqueId){};
    virtual ~ABSFeature(){};
}; // eo class ABSFeature

And onwards for any kind of feature that the car can have. Note I would not class wheels as a feature because, well, all cars have wheels although the number may differ. I am referring to various traits that can differ wildly such as electronic doors, ABS, etceteras. Suddenly, your car becomes a much simpler container:

class Car
{
private:
    int m_nWheels;
    std::string m_Colour;
    std::vector<Feature> m_Features;

protected:

public:
    Car();
    ~Car();

    void addFeature(Feature& _feature) {m_Features.push_back(_feature);};
    Feature getFeature(int _featureId) const;

    void lockDoors()
    {
        DoorsFeature& doorsFeature(static_cast<DoorsFeature&>(getFeature(DoorsFeature::UniqueId)));
        doorsFeature.lockDoors();
    } // eo lockDoors
}; // eo class Car

Given this, you can also go a step further and introduced named feature-sets (much like the option packs you get from a dealer/manufacturer) that can be automatically applied to a car, or range of makes, models and series.

Obviously, I've left a lot out. You may want to pass a reference to the car to each feature, or do otherwise.

Upvotes: 0

Peter Alexander
Peter Alexander

Reputation: 54270

You want to use policy classes:

class FourDoorPolicy { Door m_doors[4]; ... };
class TwoDoorPolicy { Door m_doors[2]; ... };

class AutoDoorLockingPolicy { ... };
class ManualDoorLockingPolicy { void lockDoors(); ... };

class FourWheelDrivePolicy { TransmissionShafts m_shafts[4]; ... };
class TwoWheelDrivePolicy { TransmissionShafts m_shafts[2]; ... };

template <class DoorPolicy = TwoDoorPolicy,
          class LockingPolicy = ManualDoorLockingPolicy,
          class DrivePolicy = TwoWheelDrivePolicy>
class Car : public DoorPolicy, public LockingPolicy, public DrivePolicy
{
  ...
};

Put all the policy specific stuff (e.g. lockDoors() function) inside the policy classes rather than the Car class. The Car class inherits these, which is a form of composition (i.e. you are building all their functionality into the Car class).

Note that you should give all the policy classes a protected, non-virtual destructor so that they can only be instantiated as part of a derived class.

You then instantiate customised cars in the normal template manner:

Car<FourDoorPolicy, AutoDoorLockingPolicy, TwoWheelDrivePolicy> myCar;

Of course, you can use typedefs to help with this (and template aliases in C++0x will help a lot, too).

See: Policy-based Design

Upvotes: 7

user229044
user229044

Reputation: 239291

The problem as I see it is that you're trying to define a single class which is capable of representing all possible version of a "Car", meaning that each instance contains member data capable of representing all possible cars. This problem was solved eons ago by traditional inheritance.

Define the functionality common to all cars in the base class. Then derive specific classes which add functionality (and member variables which increase the memory footprint). You minimize your memory simply by instantiating the proper sub class. Each instance contains only the members important to that specific type of Car.

Upvotes: 0

Bill
Bill

Reputation: 14685

Try rewriting your code to use vector instead of arrays. You can use just the space you need, and it's easier too.

#include <vector>
#include <memory>
class Car
{
public:
  int getDoorCount() { return doors.size(); }
  bool isFourWheelDrive() { return transmissionShafts.size() == 4; }
  bool areDoorsAutoLocking() { return automaticDoorLocking.get() != NULL; }
  void lockDoors() {
    if (automaticDoorLocking.get() != NULL) {
      automaticDoorLocking->lockDoors();
    } else {
      // Do manual door locking
    }
  }

private:
  std::vector<Door> doors;
  std::vector<TransmissionsShafts> transmissionShafts;
  std::auto_ptr<DoorLockingElectronic> automaticDoorLocking;

};

Notice how Car now supports hatchbacks (5 doors).

Upvotes: -1

icecrime
icecrime

Reputation: 76755

You probably should look into Policy-based design. Basically, it consists as externalizing behaviors in policy classes and instantiating a template car object with the appropriate policies. A policy class is responsible for the encapsulation of a given behavior.

From an implementation point of view : Car becomes a template where each type argument corresponds to a given policy (for example : DoorLockingPolicy). Your car template can then be "configured" depending the types you choose to instantiate it with : ManualDoorLockingPolicy or AutomaticDoorLockingPolicy.

template<class DoorLockingPolicy /*, class DoorsPolicy, ... */>
class Car : DoorLockingPolicy
{
public:
 void lockDoors()
 {
  /* ... */
  DoorLockingPolicy::lockDoors();
 }
};

struct ManualDoorLockingPolicy
{
 void lockDoors() { /* ... */ }
};

struct AutomaticDoorLockingPolicy
{
 void lockDoors() { /* ... */ }
};

int main()
{
 Car<ManualDoorLockingPolicy> car1;
 Car<AutomaticDoorLockingPolicy> car2;
}

From a performance point of view, policy-based design is a great way to achieve "don't pay for what you don't use" :

  • Calls to the policy classes can be inlined and introduce no additional cost
  • The Car template can inherit privately from its policies and benefit from the empty base optimization.

Once again, Modern C++ Design (Andrei Alexandrescu) is a great read on this topic.

Upvotes: 1

Related Questions