Requirements for an hierarchy design in C++ for integer C types extension. Interface design problem

  softwareengineering

I’m trying to design a hierarchy classes that extends the integers C types. What I’m trying to achieve is basically the following:

I have a basic class called Integer this class is an abstract class, the only thing I wish it would provide is basically the basic operations between integers, namely +,-,*,/,&,^,|,~,etc if I'm missing something, just the interface no implementation. Then I would subclass such class with StaticInteger and DynamicInteger the difference is that the StaticInteger uses templates to describes the size of the type at compile time, while DynamicInteger the size can be changed at run time.

Some use case would be the following for both classes:

StaticInteger<14> is1 = 3;
StaticInteger<32> is2 = 15;
StaticInteger<16> is3 = is1 + is2;

DynamicInteger id1;
id1.setWidth(14);
id1.setWidth(16);
id1 = is1;

Internally the data is represented by using arrays, so for example the resizing in the dynamic type would be achieved using new/delete operators;

Basically what puzzles me is how to properly design the interface of the base abstract class Integer. Let’s say I would design straightly the class StaticInteger I woudl try to do something like:

template <int L>
class StaticInteger {
public:
  //bla bla
  template <int M>
  StaticInteger<L> operator+(StaticInteger<M> x);
private:
  //bla bla
};

The dynamic instead would be

class DynamicInteger {
 public:
   DynamicInteger operator+(DynamicInteger x);
 private:
};

but how would I specify the interface that would cover any possible interface of such operator? Between static and dynamic I think is clear that in general for a given operator the algorithm implementation could differ.

Just a further note, I do understand there’s probably a philosophical problem behind what I’m trying to do, since an operator is actually a function in C++, and to be properly defined it requires all the types specified a priory, in the base class.

Update : Just an alternative design, I was thinking maybe I could abstract the concept of operations using a separate class (like a strategy pattern).

Separation of the data Integer from the operations

5

There would be an easy way out of this, that could avoid you defining all sorts of overloads for operator+ in DynamicInteger: just define a constructor based on a StaticInteger, and let the compiler try to organize implicit conversions:

class DynamicInteger {
 public:
    template <int M>
    DynamicInteger (StaticInteger<M> m ) {
           cout<<"construct Dynamic from "<< M<<endl;  // for demo purpose
    } 
    DynamicInteger operator+(DynamicInteger x) { ... }
 ...
};

Then you can write the following:

StaticInteger<16> a = 10, b=20; 
DynamicInteger x = a;         // coonvert a StaticInteger value to DynamicInteger
DynamicInteger z = x + b;     // mix StaticIntger in expressions to get them converted

Demo

Additional “philosophical” considerations (see discussion in the comments)

You don’t need a common base class nor build a hierarchy. A proper definition of the operators, including conversion and eventually operators mixing types, is sufficient.

This doesn’t prevent you from from using a common base class if you think it may help. You could for example decide to implement first a DynamicInteger , and then let the StaticInteger inherit from it (e.g.predefining the size at construction and raising an overflow instead of increasing dynamically the size). On the other hand, a pure template type would have the advantage of higher performance thanks to compile time optimizations and lighter memory management with fixed (templatized) size arrays.

Philosophically speaking, you don’t need a hierarchy either: you just need to define the rules. Ok, you can add, multiply, divide integer, fractional , real and even complex numbers and mix them as suit; they have in common that they are all numbers. But on the other hand you can also add, multiply, divide vectors, which are not numbers but an abstract mathematical concept. You can also divide an apple pie by a number, despite the fact that they do not have a common ancestor.

Finally, let’s have a look at the list<Integers>. First note that the standard C++ doesn’t provide this kind of universality for the basic types : you’d have to choose a specific type at compilation time. If you don’t want to make the decision, you could defer it to exectution, which means to create a more complex object, such as a union or a variant. For your generalized integer list, the situation is the same. So do you really need this kind of generalization ? If yes, you could indeed use a common base class but with the advantages and inconvenient that I already mentioned.

9

LEAVE A COMMENT