0

The project that I'm working on could benefit from having the possibility to easily swap between different big number libraries: GMP, OpenSSL, etc. My current implementation defines a template abstract base class in which I implement all the required operators (just to have some syntactic sugar) and I define the required pure virtual functions.

For each library, I have a derived class like this: class BigNumberGmp : public BigNumberBase<BigNumberGmp>. I know it kinda' breaks OOP, but the C++ covariance functionality is too restrictive and it does not allow me to return BigNumberBase objects from methods, only references, which is quite undesirable...

The problem is that I want the code that uses my custom wrappers to be able to work with any such wrapper: BigNumberGmp, BigNumberOpenSsl, etc. In order to achieve this, I defined typedef BigNumberGmp BigNumber and put it inside some conditional macros, like so:

#if defined(_LIB_GMP)
    typedef BigNumberGmp BigNumber;
#endif

Also, I include the appropriate headers in a similar fashion. This implementation requires that I define the _LIB_GMP symbol in the compiler options.

As you can see, this is a rather hackish technique, that I'm not really proud of. Also, it does not hide in any way the specialized classes (BigNumberGmp, BigNumberOpenSsl, etc). I could also define the BigNumber class multiple times, wrapped in _LIB_XXX conditional macros or I could implement the required methods inside the BigNumber class multiple times, for each library, also wrapped in the _LIB_XXX conditional macros. These two latter ideas seem even worse than the typedef implementation and they will surely mess up the doxygen output, since it will not be able to figure out why I have multiple items with the same name. I want to avoid using the doxygen preprocessor, since I still depend on the _LIB_XXX defines...

Is there an elegant design pattern that I could use instead? How would you approach such a problem?

Mihai Todor
  • 8,014
  • 9
  • 49
  • 86
  • I think `_LIB_GMP` is a reserved name for the implementation anyway. – Flexo Apr 29 '12 at 14:32
  • I think the RightWayToDoIt is to use a build system that allows you to have optional packages and preprocess a config.h or something like that. Autohell and CMake come to mind, but I'm sure there are others. – Robert Mason Apr 29 '12 at 14:40
  • @awoodland: Google doesn't mention any "_LIB_GMP", so I suppose it's safe. Also, a grep inside GMP doesn't yield anything... – Mihai Todor Apr 29 '12 at 14:50
  • @Robert: I tried to mess around with CMake, but I found it rather complicated for my needs... This project also needs to work on multiple platforms (Windows and Linux), so my best bet is Visual Studio and a custom Makefile. – Mihai Todor Apr 29 '12 at 14:52
  • @MihaiTodor - Just because google doesn't find any hits doesn't mean it's OK, it starts with an underscore followed by a capital letter so falls foul of [this rule](http://stackoverflow.com/a/6756290/168175) ([more info](http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier/228797#228797)) – Flexo Apr 29 '12 at 14:53
  • @awoodland: I understand. Then what would I suggest to use? – Mihai Todor Apr 29 '12 at 14:54
  • @MihaiTodor - CMake handles multiple optional packages really well. With CMake you can also generate the vsproj files so you only need to maintain one. As your project gets bigger and bigger the multiple build systems will get to be more and more of a pain. I used to agree that it was more complicated than I needed, but I only needed to spend a couple of days getting myself familiar and I've never looked back. – Robert Mason Apr 29 '12 at 15:00
  • @Robert: I completely agree with you, but I'm the only one working on this and the requirements will vary a lot in the near future so I'm trying to solve one problem at a time, because otherwise I won't get anything done :) – Mihai Todor Apr 29 '12 at 15:07
  • Are you sure virtual functions are a sensible implementation for something with as many calls as arithmetic types? I would have thought that this is ruled out completely and all wrappers I have seen purely use templates and template algorithms on the number type to use. Is there really a scenario where you expect to switch the NT at run-time? Also, have a look at the boost.multiprecision review which recently took place. – pmr May 06 '12 at 01:34
  • @pmr: I am not sure. I am still pondering on which design to choose before I modify my implementation. Since I'm working with 1024 bit numbers, my guess is that the computational cost of virtual functions is dwarfed by the arithmetic operations... Also, I'm not planning to switch the library during run-time since it would be pointless. – Mihai Todor May 06 '12 at 12:31

2 Answers2

1

The way I would do it is with the help of the pimpl idiom. I would first define a wrapper class:

class BigNumber {
private:
    class BigNumber_impl {
        virtual void do_something() = 0;
    }
    BigNumber_impl * impl;
public:
    void do_something() {
        impl->do_something();
    }
};

And then I would have BigNumberGMP, etc. inherit from BigNumber::BigNumber_impl. Then, you can return objects of BigNumbers but still have polymorphism under the hood.

This doesn't solve the issue of creating BigNumbers, and you also have to worry about how to add BigNumbers with different BigNumber_impl types. So your original solution may be good for your purposes. You will, at some point, have to rely on some preprocessor magic.

Robert Mason
  • 3,949
  • 3
  • 31
  • 44
  • Somehow, I overlooked this kind of implementation. Thanks for the tip. I'm trying right now to see if I can accommodate this design into my code. – Mihai Todor Apr 29 '12 at 16:23
1

It looks like you're going to recompile each time you switch libraries in which case you can use template specialisation instead of inheritance.

Choosing which to use would be pretty much as you have it (something #if based), but you'd save the virtual members which means the compiler can still inline which means it could be significantly faster for some cases.

First of all, take structs that describe each of the implementations. In here you can put the basic API names that work in the same sort of way across all of the libraries. For example, if they all support an add operation that takes pointers to two big nums and returns a pointer to a new big num that contains the result you can do something like this:

(Note that I've not run this through a compiler, and I don't know what the actual APIs look like, but it should be enough to give a general idea of the approach)

struct GMP {
    GMP_ptr* add(GMP_ptr *l, GMP_ptr*r) {
        return GMPadd(l, r);
    }
};
struct OpenSSL {
    OpenSSL_ptr* add(OpenSSL_ptr*, OpenSSL_ptr*) {
        OpenSSL_ptr ret = NULL;
        OpenSSLadd(l, r, &ret);
        return ret;
    }
};

Now we can define a common super class that contains the use of these easy to map APIs:

template< typename B, typename R >
class common {
public:
    // Assume that all types have the same API
    R operator + (const common &r) {
        return R(B::add(l.ptr, r.ptr));
    }
};

The type B is the struct that defines the big number API, and the type R is the real implementation sub-class. By passing in R like this we solve the co-variant return problem.

For the real implementation we define a template that will do the work for us:

template< typename B >
class big_num;

Now we can specialise this for the implementations:

template<>
class big_num<OpenSSL> : common< OpenSSL, big_num<OpenSSL> > {
    OpenSSL_ptr *ptr;
public:
    big_num(OpenSSL_ptr*p)
    : ptr(p) {
    }
    big_num(const char *s)
    : ptr(OpenSSLBigNumFromString(s)) {
    }
    ~big_num() {
        OpenSSLBigNumFree(ptr)
    }
};

The operator + will come from the super class, and you can now use them like this:

void foo() {
    big_num< GMP > gmp1("123233423"), gmp2("234");
    big_num< GMP > gmp3 = gmp1 + gmp2;
    big_num< OpenSSL > ossl1("1233434123"), ossl2("234");
    big_num< OpenSSL > ossl3 = ossl1 + ossl2;
}

The advantage here is that there is minimum code duplication between specialisations due to the use of the structs to adapt between similar API features and a common implementation in one template. The specifics for a given API are now in the template specialisations, but there are no virtuals and there is no common super class. This means that the compiler can inline pretty much everything in your wrappers which will make them essentially as fast as they can be.

Due to the specialisation you also potentially have access to all of the implementations which may make your unit tests much easier to write/manage (you should be able to write templated versions of those too).

If you only want one of them to be visible then something like this:

#if BIGNUM=="GMP"
    typedef big_num<GMP> used_big_num;
#elif BIGNUM=="OpenSSL"
     typedef big_num<OpenSSL> used_big_num;
#endif

You may also need to put guards around the specialisations too if the headers aren't always available in which case you'll want a set of HAVE_GMP_BIGNUM and HAVE_OPENSSL_BIGNUM macros too.

KayEss
  • 2,290
  • 15
  • 31
  • What do you mean by template specialization in this case? I don't think I want to over-complicate my code by making BigNumber a template. Also, I will have to repeat tons of code in every specialization (i.e. every operator overload), which is something that I'm trying to avoid. – Mihai Todor Apr 29 '12 at 16:31
  • You can still inherit from a template that contains the common code, but really it's a trade off for speed. The templated version will inline properly (unlike the vtable based approach) and will potentially save a load of function calls (as the thin wrappers will get optimized away). This may give you a good performance boost at essentially no extra code complexity. – KayEss May 02 '12 at 02:14
  • I know I'm asking a lot, but could you please provide a sketch or just specify more concretely how one would implement the pattern that you are describing? For some reason, I'm having a hard time visualising it... – Mihai Todor May 02 '12 at 11:24
  • I've added a brief sketch of the sort of thing I had in mind. Hope it's more or less clear. – KayEss May 06 '12 at 01:13
  • OK, maybe this is a stupid question, but how would you make void foo() be implementation neutral? Let's say that, initially, I make the internal logic inside foo() rely only on big_num< GMP >, but, afterwards, I notice that I only have OpenSSL support on another platform. I want to have a single big_num class, which is not a template and it can do its job regardless of the underlying big number library. I think I should be able to achieve this with the PIMPL idiom, as described by Robert above. – Mihai Todor May 06 '12 at 11:40
  • Use the same sort of typedef controlled by a macro that you had before. I'll add that in. – KayEss May 06 '12 at 13:12
  • Yeah, well, I was hoping to avoid doing this in such a fashion, so that I would mask the particular implementations completely. Now, it looks more or less similar to what I created, but without inheritance. Do you think that by removing the inheritance pattern, I will gain a big increase in speed, considering that the costly operations should be the actual arithmetics? – Mihai Todor May 06 '12 at 16:59
  • 1
    That depends on a number of factors I think. You may get smaller code which leaves more CPU cache available for the numbers, you may get better locality which will improve data pre-fetching (especially when used in containers like `std::vector` if the underlying implementation can avoid heap allocations in some circumstances, i.e. for small numbers). You may get better productivity as you can wrap all of the implementations into a single library and so avoid recompiles of the wrapper when you want to switch between them in the application code. – KayEss May 08 '12 at 04:50