Question

Many libraries I have seen/used have typedefs to provide portable, fixed size variables, eg int8, uint8, int16, uint16, etc which will be the correct size regardless of platform (and c++11 does it itself with the header stdint.h)

After recently using binary file i/o in a small library I'm writing I can see the benefit of using typedefs in this way to ensure the code is portable.

However, if I'm going to the trouble of typing "namespace::uint32" rather than using built in fundamental types, I may as well make the replacement as useful as possible. Therefore I am considering using classes instead of simple typedefs.

These wrapper classes would implement all normal operators so could be used interchangeably with the fundamental type.

Eg:

int x = 0;
//do stuff

could become

class intWrapper {
//whatever
};

intWrapper = 0;
//do stuff

without having to modify any code in "//do stuff"

The reason I'm considering this approach as opposed to just typedefs is the fact I already have functions that operate on fundamental types, eg

std::string numberToString(double toConvert);

std::string numberToHexString(double toConvert);

int intToXSignificantPlaces(const int& number, 
                               unsigned char numberOfSignificantPlaces);

bool numbersAreApproximatelyEqual(float tollerance);
//etc....

Syntactically it would be nicer (and more oop) to do the following:

intWrapper.toString();
intWrapper.toHexString();
//etc

Also it would allow me to implement bigint classes (int128, etc) and have those and the smaller ones (based on fundamental types) use identical interfaces.

Finally each wrapper could have a static instance of itself called max and min, so the nice syntax of int32::max and int32::min would be possible.

However, I have a few concerns that I would like to address before doing this (since it is mostly syntactical sugar and these types would be used so commonly any extra overhead could have a significant performance impact).

1) Is there any additional function calling overhead when using someClass.operator+(), someClass.operator-() etc over just int a + int b? If so, would inlining operator+() eliminate ALL this overhead?

2) All external functions require the primitive type, eg glVertex3f(float, float, float) could not simply be passed 3 floatWrapper objects, is there a way to automatically make the compiler cast the floatWrapper to a float? If so, are there performance impacts?

3) Is there any additional memory overhead? I understand(?) that classes with inheritance have some sort of virtual table pointer and so use slightly more memory (or is that just for virtual functions?), but assuming these wrapper classes are not inherited from/are not child classes there isn't any additional memory use using classes instead of fundamental types, is there?

4) Are there any other problems / performance impacts this could cause?

Was it helpful?

Solution

1) Is there any additional function calling overhead when using someClass.operator+()

No, if the function body is small and in the header, it will be inlined, and have no overhead

2) Is there a way to automatically make the compiler cast the floatWrapper to a float?

struct floatWrapper {
    floatWrapper(float); //implicit conversion from float
    operator float(); //implicit conversion to float.  
};

Again, if the body of the function is small and in the header, it will be inlined, and have no overhead

3) Is there any additional memory overhead?

not if there's no virtual functions. A class is called polymorphic if it declares or inherits any virtual functions. If a class is not polymorphic, the objects do not need to include a pointer to a virtual function table. Moreover, performing dynamic_cast of a pointer/reference to a non-polymorphic class down the inheritance hierarchy to a pointer/reference to a derived class is not allowed, so there is no need for the objects to have some kind of type information.

4) Are there any other problems / performance impacts this could cause?

performance? No.

Also, be sure to implement binary operators that don't modify the lhs as free functions, and overload them to support all relevant permutations of floatWrapper and float.

struct floatWrapper {
    explicit floatWrapper(float);
    operator float(); //implicit conversion to float.  
    floatWrapper operator-=(float);
};
floatWrapper operator-(floatWrapper lhs, floatWrapper rhs) 
{return lhs-=rhs;}
floatWrapper operator-(float lhs, floatWrapper rhs) 
{return floatWrapper(lhs)-=rhs;}
floatWrapper operator-(floatWrapper lhs, float rhs) 
{return lhs-=rhs;}

Here's my attempt at such a thing. Note you'll need a slightly different version for float/double/long double.

OTHER TIPS

It depends on the compiler. If it has loops or allocations, less likely to be inlined.

I think the answers are not completely correct - at least for gcc 4 I have observed a significant overhead due to the constructor and operator calls.

The following takes about twice as long as with long:

typedef intWrapperImpl<long> TestWrapper;
//typedef long TestWrapper;

int main (int argc, char** argv) {
    TestWrapper sum(0);
    TestWrapper test(4);

    for (long i = 0; i < 1000000000L; ++i) {
        sum += test;
    }

    cout << sum << "\n";

    return 0;
}

Using different versions of gcc 4 with and with out optimization led to no differences in the performance.

In this case, adding

intWrapperImpl& operator+=(const intWrapperImpl & v) {value+=v.value; return *this;}

gives only a slight improvement.

Using such a wrapper as if they were base-types in performance critical code seems to be a bad idea. Using them locally and thereby invoking the constructor all the time seems even worse.

This really came as a surprise to me, since it should easily be possible to inline everything and optimize it as if it would be a base-type variable.

Any further hints would be greatly appreciated!

The following simple wrapper might just work:

class intWrapper {
private:
  int val;
public:
  intWrapper(int val = 0) : val(val) {}
  operator int &() { return val; }
  int* operator &() { return &val; }
};

To make the above code more generic:

template <class T>
class PrimitiveWrapper {
private:
  T val;
public:
  PrimitiveWrapper(T val = 0) : val(val) {}
  operator T &() { return val; }
  T* operator &() { return &val; }
};

where T is the primitive type. Example usage:

int main() {
  PrimitiveWrapper<int> a = 1;
  a += 1;
  std::cout << a << std::endl;
}

I tried with gcc for the above code, and I see the overhead being completely optimized out. This is reasonable because the wrapper is extremely simple. However, this may differ from compiler to compiler, and optimization might become weaker when the logic becomes more complicated.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top