13

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?

jtedit
  • 1,450
  • 2
  • 13
  • 27
  • 1
    Worth mentioning that at least a few C++ luminaries consider your idea to be poor C++ style. See http://stackoverflow.com/questions/5989734/ – Nemo Jul 22 '13 at 17:46
  • Yes, there is an overhead. Even if the functions are inline, there is still the constructor call when you create the object. If you are creating an array of these objects then its easily much slower. –  Mar 06 '21 at 07:44

4 Answers4

14

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.

Jens Müller
  • 302
  • 4
  • 13
Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • Just make sure all the function definitions are in-class in the header files, so the compiler can actually inline them – Erbureth Jul 22 '13 at 17:11
  • Okay, thanks for the info. As a side note, what is defined as "small" when it comes to inlining functions? Is it one with less than X lines? One without any memory allocations/only manipulates a classes member variables? Some other criteria? Or does it vary from compiler to compiler? – jtedit Jul 22 '13 at 17:19
  • 1
    @jtedit It is compiler-specific, as it has its own heuristics to determine, whether inlining would be faster or not. Note that the `inline` keyword doesn't actually make the compiler inline the function. – Erbureth Jul 22 '13 at 17:25
  • 1
    @jtedit It's not defined, but when using g++ you can add -Winline, which will warn you when function is too big to be inlined. – etam1024 Jul 22 '13 at 17:50
  • @MooingDuck I think in this case you want to remove `explicit` from the constructor. If I had `void foo(floatWrapper);`, I would expect that calling it by `foo(5);` should be valid. `explicit` forces me to write `foo(floatWrapper(5));`. – etam1024 Jul 22 '13 at 17:57
  • @MooingDuck Would you mind if I borrow your fundamental type wrapper for something I am working on? –  Sep 26 '13 at 18:00
  • 1
    @KevinCadieux: I think StackOverflow answers are inherently free game. Have at. – Mooing Duck Sep 26 '13 at 18:56
  • I'd like to know more about point 3. Why is there no space overhead if there are no virtual functions? Unfortunately, I only have the C++98 standard available. In § 10.3 [class.virtual] it says: "A class that declares or inherits a virtual function is called a _polymorphic class_. OK so far. But wouldn't type information be necessary to be able to try a dynamic cast to a derived class? I could not find a statement in the standard why such a dynamic cast is not allowed for a non-polymorphic class. – Jens Müller Aug 10 '14 at 14:30
  • 1
    Actually, it was the C++03 standard, and I now found the reason in clause 6 of § 5.2.7 [expr.dynamic.cast]: "Otherwise, v shall be a pointer to or an lvalue of a polymorphic type (10.3)." -> So if casting down the inheritance hierarchy, the static type of the argument to dynamic_cast must be polymorphic. – Jens Müller Aug 10 '14 at 14:51
  • Can anybody help me to add float and int using code given at http://coliru.stacked-crooked.com/view?id=f5566f15c11c52e2db7189d602cc601a-f674c1a6d04c632b71a62362c0ccfc51 I have created floatWrapper as new typedef but how to create friend for +(float,int) or +(int,float). Thank you. – Deepak Ingole Feb 22 '17 at 14:19
  • Dear Duck, thank you very much for the reply. Now, I am facing another problem that it throws an error when I declare intWrapper a = 42; or intWrapper b = 999999999; Also, how can I add int and mytype (my customized type which is converted from int) .I mean I want to first convert int to mytype and add in mytype and show result in mytype. Please see this [code](http://coliru.stacked-crooked.com/a/b5a7e35d9dfeaa30). Please help me. – Deepak Ingole Feb 22 '17 at 21:38
  • @DeepakIngole: I had disabled `intWrapper a = 42;` syntax by adding the `explicit` keyword to the constructor, because it tends to cause problems with wrappers. However, at your request, I've disabled it. There's no trick for `mytype` other than to make it exist: http://coliru.stacked-crooked.com/a/87f16bc6680dd307 – Mooing Duck Feb 22 '17 at 21:50
  • Thanks Duck. Actually, I have mytype and I have function x2mytype(double *a, mytype *b) which converts double a to mytype b. I am not getting where to put this function so that I can do e.g. mytype c = mytype c + b. Following is working if both are mytype and put line in code template intWrapperImpl& operator+=(U v) {mytypeadd(&value, value, v); return *this;} but when both numbers are not of mytpe then it throws error of conversion. where to do conversion. Suppose where to put x2mytpe in the line template intWrapperImpl& operator+=(U v) {mytypeadd(&value, value, v); – Deepak Ingole Feb 22 '17 at 22:42
  • @DeepakIngole: `x2mytype(double *a, mytype *b)` is a weird way to convert, I don't know why you would do that. `mytype c = mytype c + b` that's not valid syntax, but for `mytype c = a + 3.0;` where `a` is a `mytype`, that already works fine: http://coliru.stacked-crooked.com/a/067acfa1b33b1332 – Mooing Duck Feb 22 '17 at 23:49
  • Can somebody please help me to define line 18 and 19 of the [code](http://coliru.stacked-crooked.com/a/0c0d7ed6f8f82f5c) so that 18 will work for ubound_t a+= ubound_t a and 19 for ubound_t a+= double b; – Deepak Ingole Feb 27 '17 at 16:31
  • @DeepakIngole: I still don't really understand what you're trying to do. This? http://coliru.stacked-crooked.com/a/b3f0a8869a35a0ae I'm sort of guessing at what your weird types and functions are, since you haven't told us. – Mooing Duck Feb 27 '17 at 19:48
  • Thanks, Duck. Actually, I am creating a wrapper for universal number format. Can you please tell me how to wrap function same as you did for the operators. For example for 'sqrt(a)'. Thank you. – Deepak Ingole Mar 02 '17 at 16:38
  • @DeepakIngole: Unfortunately, there's no real easy catch-all: `unumWrapper sqrt(const unumWrapper& v) {return sqrt(unumWrapper.get());}` – Mooing Duck Mar 02 '17 at 17:58
  • Dear Duck, would you please help me to resolve the issue of the ambiguity of operator. In the [code](http://coliru.stacked-crooked.com/a/bdd0ea5d468007c0) line 83 is ubound_t addition of float and ubound_t and save sol to ubound_t and line 165 is float addition of float and ubound_t but sol is saved in float. – Deepak Ingole Mar 06 '17 at 22:15
  • @DeepakIngole: Stop putting `ubound_t` code into the wrapper. A wrapper should be a wrapper around a logical type. http://coliru.stacked-crooked.com/a/3016f1903f77d5df – Mooing Duck Mar 06 '17 at 23:11
  • Dear Duck, thanks a lot for your efforts. It gives me [erors](http://coliru.stacked-crooked.com/a/4559d64ed9c77764) as I already have defined ubound_t and unum_t as a typedef struct in my .h file. also, why are we using class T when compiler already knows all that operators. I want to overload operators whenever there will be ubound_t data type as it is unknown for compiler. – Deepak Ingole Mar 07 '17 at 11:20
  • @DeepakIngole: yes, since I don't have your code, I wrote ubound_t and unum_t to show they should look like, regardless of a wrapper. We use `T` because the wrapper is a template around any number-like class. I tried to overload all the operators for you. I don't understand the big picture of what you're trying to do, but it sounds more and more like you don't need a generic intWrapper class, it sounds like you need a specific `ubound` class for those standalone functions, which is what I've done with my `ubound_t` class. – Mooing Duck Mar 07 '17 at 21:12
  • Dear Duck, I want to replace common math operators like +=,-+.....etc by ubound_t type. Like if ubound_t operator+=(float x, float y) {x2ub(x,&v3); x2ub(y,&v4);return v3+=v4;} where v3 and v4 are ubound_t type. It shows error 'float operator+=(float, float)' must have an argument of class or enumerated type|. Can you please help me to solve this. I want everything in ubound_t in the main.ccp where ever there is some math operator it should be done by ubound_t. – Deepak Ingole Mar 09 '17 at 22:59
  • Things like `operator+=(float x, float y)` are literally impossible. At least one side of the operator must be a user-defined type to be overloaded. Just let the float addition happen naturally, and then convert the result to your ubound_t type. However: `ubound_t operator "" _ub(float v) {return v;}` will cause `ubound_t r = 3.0_ub + 9.0_ub;` to do what you want. – Mooing Duck Mar 10 '17 at 00:06
  • How can I define in the .h file that if variable is type float consider it as a ubount_t type. I already have typedef ubound_t defined. – Deepak Ingole Mar 10 '17 at 16:24
  • @DeepakIngole: As I said: what you describe, as is, is impossible. A float is a float, and there's nothing you can do to change that directly. However, what you _can_ do is make it not a float. `ubound_t operator "" _ub(float v) {return v;}` tells the compiler how to make `ubound_t` literals in code, and then you use syntax like `3.14_ub` to make a `ubound_t` literal in your code. – Mooing Duck Mar 10 '17 at 17:51
  • I agree that we can not change float. But, can we assign float a = ubound_t b. I did ubound_t b = float a but opposite is not working. I did by intWrapperImpl& operator=(double v) {x2ub(v,&value);ubound_init(&value);return *this;} – Deepak Ingole Mar 10 '17 at 18:05
  • @DeepakIngole: In the ubound_t class, add `operator float() const {float t; ub2x(this,&t); return t;}`. The problem is such conversions tend to me various casts ambiguous, and cause problems. So prefer `explicit operator float()`, and use `float a = float(b);` (where `b` is a `unbount_t`) – Mooing Duck Mar 10 '17 at 18:11
  • Dear Duck, can you please help me to get rid of error: no match for 'operator=' (operand types are 'c_float ' and 'double') in the [code](http://coliru.stacked-crooked.com/a/7b7e2955d83f98d8). I have defined c_float as a ubound_t i.e. typedef ubound_t c_float; – Deepak Ingole Mar 13 '17 at 15:41
  • @DeepakIngole: I've deduced that `ubound_t` is _not_ a C++ numeric type, and thus the entire concept of the wrapper in this answer is unrelated to what you need. What you need is a class that wraps the `ubound_t` functions in a class to make them act like a C++ numeric type. A `ubound_class` if you will. http://coliru.stacked-crooked.com/a/7fb5905ccf387f25 – Mooing Duck Mar 13 '17 at 17:04
  • Dear Duck, thanks for the help. But, my problem is that I want to use c_float which is typedef i.e. #ifndef DFLOAT typedef ubound_t c_float; #endif and I want to do c_float a = 42; but it throws error:. conversion from 'int' to non-scalar type 'c_float {aka ubound_s}' requested| – Deepak Ingole Mar 13 '17 at 20:04
  • Also, this is not working ubound_class b = 4; c_float a; a = b; it should work as I defined typedef ubound_t c_float; why it is saying no match for operator=. Now b is ubound_t and a is also ubound_t.I just changed its name to c_float but it should be ubound_t. – Deepak Ingole Mar 13 '17 at 20:44
  • Can you edit `ubound_t` or not? For `c_float a = 42` to work, `ubound_t` needs to have a constructor that takes an integer. If you can't edit `ubound_t` directly, then that's not possible. For `ubound_class b = 4; c_float a; a = b;`, implicit conversions complicate things, so don't use them. Use `a = ubound_t(b);` instead, or better yet: `a = static_cast(b);`. – Mooing Duck Mar 13 '17 at 21:49
  • It's really contradictory because it works for ubound_class a = 4; but for c_float a =4 throws error. I just saying consider ubound_t as c_float by typedef ubound_t c_float; – Deepak Ingole Mar 13 '17 at 22:33
  • @DeepakIngole: Right. I enabled `ubound_class a = 4` because you requested it. It's safe to allow one or the other, but it causes major problems to allow both. The normal thing to do is to _disallow_ both, so that way you don't accidentally do something the caller didn't intend. – Mooing Duck Mar 13 '17 at 23:48
  • In [code](http://coliru.stacked-crooked.com/a/7fb5905ccf387f25) if we use operators +,-,*,/ in for loop it uses output of the previous loop as the lhs in current loop e.g. a = 2, b =3 so, for i=1; c = a+b is c = 5 and for i =2, c = a+b; gives c =8 where again 5 is expected it is happening for -,*,/. – Deepak Ingole Apr 01 '17 at 19:07
  • @DeepakIngole: Cannot reproduce: http://coliru.stacked-crooked.com/a/e5c4e9bd92928a7b. Works fine for me. – Mooing Duck Apr 01 '17 at 22:26
3

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

Neil Kirk
  • 21,327
  • 9
  • 53
  • 91
0

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!

mdr
  • 187
  • 1
  • 11
0

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.

xuhdev
  • 8,018
  • 2
  • 41
  • 69