1

I have an abstract class, called UsbDevice (mostly pure virtual). It doesn't do much, but the three derived classes do:

class WindowsUsbDevice : public UsbDevice
class LinuxUsbDevice : public UsbDevice
class OsxUsbDevice : public UsbDevice

each of these classes doesn't really add any public methods - instead they are just different implementations of the UsbDevice abstract class.

Now, I want to create a new class that derives from any one of these classes, let's call it FancyUsbDevice. Lets say it represents a specific device.

This new class needs to expose the full UsbDevice interface (implemented by the intermediate class), as well as expose a few fancy high-level functions that only use methods on the UsbDevice interface - no need to expose anything specific to one of those intermediate classes.

I can see three possible options, and I would like to know if they would work, and which one is the best.

A) don't inherit, instead make FancyUsbDevice HAS-A UsbDevice* instead of be IS-A. (sidestep the problem. I don't like this option, but it feels safe.)

B) Assuming I have the rare advantage of only having one of those classes actually build on any specific platform, #ifdef the inheritance string: (sidestep the problem again)

#ifdef WIN32
class FancyUsbDevice : public WindowsUsbDevice
#endif
#ifdef LINUX
class FancyUsbDevice : public LinuxUsbDevice
#endif

...

C) Templates! Inherit from the template type:

Template<typename T> class FancyUsbDevice : public T

construct it as the child type, and cast it to the interface:

FancyUsbDevice<WindowsUsbDevice> fancy_device;
FancyUsbDevice<UsbDevice> generic_fancy_device = dynamic_cast<FancyUsbDevice<UsbDevice>>(fancy_device);

however, I don't think that would work, for at least the reason that the compiler will think that generic_fancy_device is smaller than fancy_device, and all of the member variables will be offset and broken. (I think)

Thanks! Sorry for using interface / abstract / pure virtual interchangeably.

Marcus10110
  • 509
  • 4
  • 12

3 Answers3

2

Why not just

#ifdef WIN32
typedef WindowsUsbDevice FancyUsbDevice;
#endif

#ifdef LINUX
typedef LinuxUsbDevice FancyUsbDevice;
#endif

Or if you want to do it the C++11 way

#ifdef WIN32
using FancyUsbDevice = WindowsUsbDevice;
#endif

#ifdef LINUX
using FancyUsbDevice = LinuxUsbDevice;
#endif

Anyway, they are just the same.

Community
  • 1
  • 1
Mark Garcia
  • 17,424
  • 4
  • 58
  • 94
1

So if I understand correctly, you want to have a class that a) implements the interface of UsbDevice the same way the OS-specific implementations do, and b) adds some more functionality that is based on that interface.

The simplest thing yould be to not make another class but just provide that additional functionality as a set of functions, taking a UsbDevice as parameter. After all, you are not really adding new functionality, you are only composing existing functionality to new functions. If you are not creating a huge set of helper functions, that could be the right thing to do.

If it has to be a class on its own, consider making it a decorator on top of an implementation of UsbDevice. I suppose you have some factory method, giving you the right implementation depending on the OS you are on. The decorator would look like this:

class UsbMoreFuncDecorator: UsbDevice {
  std::unique_ptr<UsbDevice> deviceImpl;
public:

  UsbMoreFuncDecorator(Param param) 
    : deviceImpl(UsbDeviceFactory.create(param))  
  //  ^-- Win or OsX or Linux - doesn't matter
  {}

  // delegate the UsbDevice virtual functions to the actual implementation
  virtual void someBasicUsbDeviceFunc() final {
    deviceImpl->someBasicUsbDeviceFunc();
  }

  // use the UsbDevice functions to compose more functionality
  void someMoreFunctionality() {
    someBasicUsbDeviceFunc();
    someOtherUsbDeviceFunc();
  }
};
Arne Mertz
  • 24,171
  • 3
  • 51
  • 90
0

Option B will do exactly what you want with a minimum of fuss. Rather than use ifdef at this point, it might be cleaner somewhere else to say:

#ifdef WIN32
    typedef WindowsUsbDevice NativeUsbDevice
    // any other such typedefs needed for Windows
#endif  // and similarly for Linux and OSX

then

class FancyUsbDevice : public NativeUsbDevice

If you are using C++11, you can have "using" instead of "typedef".

Muscles
  • 471
  • 4
  • 12