4

I'm writing a C++ wrapper library around a number of different hardware libraries for embedded systems (firmware level), using various libraries from different vendors (C or C++). The API exposed by the header files should be vendor agnostic... all Vendor header libraries are not included in any of my header files.

A common pattern I have is making the vendor member data opaque, by only using a pointer to some "unknown" vendor struct/class/typedef/pod type.

// myclass.h
class MyClass 
{
 ...
private:
   VendorThing* vendorData;
};

and implementation (note: each implementation is vendor specific; all have the same *.h file)

// myclass_for_vendor_X.cpp
#include "vendor.h"
... {
   vendorData->doSomething();
or
   VendorAPICall(vendorData,...);
or whatever

The problem I have is that VendorThing can be lots of different things. It could be a class, struct, type or pod. I don't know, and I don't want to care in the header file. But if you pick the wrong one, then it doesn't compile if the vendor header file is included as well as my header file. For example, if this the actual declaration of VendorThing in "vendor.h":

typedef struct { int a; int b; } VendorThing;

Then you can't just forward-declare VendorThing as class VendorThing;. I don't care about what the type of VendorThing is at all, all I want is the public interface to think of it as void * (i.e allocate space for a pointer and that is it), and the implementation think of it using the correct pointer type.

Two solutions I have come across are the "d-pointer" method found in Qt, where you add a level of indirection by replacing VendorThing a new struct VendorThingWrapper

// myclass.h
struct VendorThingWrapper;
class MyClass 
{
 ...
private:
   VendorThingWrapper* vendorDataWrapper;
};

and in your cpp file

// myclass.cpp
#include "vendor.h"
struct VendorThingWrapper {
   VendorThing* vendorData;
};

... {
   vendorDataWrapper->vendorData->doSomething();
}

but this adds a second pointer dereference, which is not a huge deal, but as this is targeting embedded systems, I don't want to add that overhead just because the language can't do what I want.

The other thing is just declare it void

// myclass.h
class MyClass 
{
 ...
private:
   void* vendorDataUntyped;
};

and in the implememtation

//myclass.cpp
#include "vendor.h"
#define vendorData ((VendorThing*)vendorDataUntyped)

 ... {
   vendorData->doSomething();
}

but #define's always leave a bad taste in my mouth. There must be something better.

Mark Lakata
  • 19,989
  • 5
  • 106
  • 123
  • Have you considered using an abstract baseclass with different implementations instead? Optionally, implement a factory function, so only its code needs to know the actual type. – Ulrich Eckhardt Jul 27 '14 at 05:44
  • 1
    If `VendorThing` is defined in a header file then this is not a pImpl idiom... in a pImpl, it points to a class that only exists within one `.cpp` file. This is just containment of VendorThing. Your first suggestion is a pImpl, and I think you are overstating the "problems" of two levels of indirection. How about making `VendorData` have type `VendorThing` (not pointer)? – M.M Jul 27 '14 at 05:58
  • @UlrichEckhardt - There will only be one vendor's code implemented at any one time, at compile time. Having virtual functions just adds another level of pointer dereferencing. – Mark Lakata Jul 27 '14 at 06:00
  • @MattMcNabb - I don't have control over VendorThing -it's a third party library. I'm trying to make *my* interface pimpl, so it hides the third party implementations. – Mark Lakata Jul 27 '14 at 06:02
  • @MarkLakata You do have control over `vendorData` – M.M Jul 27 '14 at 06:04
  • Avoiding virtual functions is indeed a reason, but have you measured the performance impact they have, especially when compared to the indirect PIMPL call? Anyhow, you need something in your (only/base-) class that can store any kind of vendor-object. To some extent, Boost.Any can do that, though I believe it requires copyability of the contained objects. If that doesn't help, you could still go the route of creating a char array in the base and then in-place constructing the vendor object in the constructor on top of it. – Ulrich Eckhardt Jul 27 '14 at 06:14
  • This is all targeted towards firmware, where every cycle (especially in an interrupt handler) and every memory location count. I may have 8KB of code space and 4KB of RAM. Most people don't write firmware in C++ for this reason. I'm trying this out. – Mark Lakata Jul 27 '14 at 06:52
  • @MattMcNabb - If I make `VendorThing vendorData;` a private member function, then I need to put `#include "vendor.h"` in "myclass.h", which goes against the whole purpose of making my header agnostic of vendor. – Mark Lakata Jul 27 '14 at 06:54
  • @MarkLakata You didn't mention so far that `VendorThing` was a function. But that does not matter either way, you do not need `#include "vendor.h"` in "myclass.h". "myclass.h" does `#include "myclass.cpp"`, and myclass.cpp does `#include "vendor.h"`. The name `VendorThing` should only appear within `myclass.cpp` . – M.M Jul 27 '14 at 06:58
  • @MattMcNabb - Sorry for confusion. You said `VendorThing`, but you were referring to the second mention of that word, not the first one. Yes, now I understand. Your suggestion is the same as jxh's answer below. – Mark Lakata Jul 27 '14 at 07:04
  • Ah, so it is. Sorry for the confusion – M.M Jul 27 '14 at 07:11

2 Answers2

1

If you are willing to go the route of the VendorThingWrapper, then you simply allow the wrapper to contain the data itself, rather than a pointer to it. This gives you the abstraction layer and avoids the extra dereference.

// myclass.cpp
#include "vendor.h"
struct VendorThingWrapper {
   VendorThing vendorData;
};

... {
   vendorDataWrapper->vendorData.doSomething();
}
jxh
  • 69,070
  • 8
  • 110
  • 193
  • I thought of this. The problem gets a little messy when you *want* to treat vendorDataWrapper as a pointer, for example assign a value to it. Then you have to make a cast. For example, if the vendor API gives you a pointer to VendorThing, you would have to do `vendorDataWrapper = reinterpret_cast(ptr);` Do able, but still leave me feeling dirty. – Mark Lakata Jul 27 '14 at 06:17
  • As long as the vendor API is passing back the same pointer you passed to it, there is no issue here. If the vendor API is passing back a different pointer, then you probably want to copy out the contents anyway. – jxh Jul 27 '14 at 06:18
1

You can avoid the additional pointer dereference by using:

#include "vendor.h"

struct VendorThingWrapper : public VendorThing {};

Of course, at that point, it makes more sense to use the name MyClassData instead of VendorThingWrapper.

MyClass.h:

struct MyClassData;

class MyClass
{
   public:

      MyClass();
      ~MyClass();

   private:
      MyClassData* myClassData;
};

MyClass.cpp:

struct MyClassData : public VendorThing {};

MyClass::MyClass() : myClassData(new MyClassData())
{
}

MyClass::~MyClass()
{
   delete myClassData;
}

Update

I was able to compile and build the following program. The unnamed struct is not a problem.

struct MyClassData;

class MyClass
{
   public:

      MyClass();
      ~MyClass();

   private:
      MyClassData* myClassData;
};


typedef struct { int a; int b; } VendorThing;

struct MyClassData : public VendorThing
{
};

MyClass::MyClass() : myClassData(new MyClassData())
{
   myClassData->a = 10;
   myClassData->b = 20;
}

MyClass::~MyClass()
{
   delete myClassData;
}

int main() {}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • I'm pretty sure that only works if `VendorThing` is a C++ class. In my specific case, it is an typedef to unnamed struct (ie C). – Mark Lakata Jul 27 '14 at 06:05
  • @MarkLakata, that is not a problem. Take a look at my update. – R Sahu Jul 27 '14 at 06:12
  • this is looking better. It does actually work. The only thing now is to figure out how to automatically downcast, so that I can do something like `myClassData = new VendorThing()` for example. I can static_cast this, but I think it is UB. (see http://stackoverflow.com/questions/24978194/better-way-of-using-an-opaque-pointer-for-pimpl/24978317#24978317) – Mark Lakata Jul 27 '14 at 06:33
  • @MarkLakata, but there is no need. You have access to all the member data of `VendorThing` from `myClassData`. Not only that, you can additional data to `MyClassData` if there's a need. – R Sahu Jul 27 '14 at 06:39
  • But only if I have the liberty of constructing `myClassData` on my own terms. The pointer value in question is a fixed memory location. In my particular case, the vendor.h gives me `#define SPI1 ((SPI_TypeDef *) 0xE3EE0000)` and I need to assign it to `myClassData`. – Mark Lakata Jul 27 '14 at 06:48
  • @MarkLakata, you have full control over how you wish to initialize `myClassData` and what to do with it in the destructor. – R Sahu Jul 27 '14 at 06:51
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/58091/discussion-between-mark-lakata-and-r-sahu). – Mark Lakata Jul 27 '14 at 06:56