const float&

Started by
31 comments, last by Antheus 16 years ago
I'm having a mild disagreement with another coder, and I'd like people's thoughts. Suppose I had a function that took 3 floats as arguments. (3 floats being the same size as 3 pointers to floats). Is it slower to do:

void Func(const float &A, const float &B, const float &C)
instead of

void Func(float A, float B, float C)
I'm thinking not, but my friend says that the former generates extra instructions in the assembly. I don't know enough assembly to say one way or another. The other issue was my adding the const to everything, even if it's not passed by reference. Since the floats are passed by value in the latter method, you don't need consts and using consts is a "misuse of the concept of const-correctness." Obviously it doesn't really matter. Our project isn't CPU bound anyway, and any differences are bound to be trivial. I'm just curious if I've been doing things backwards all this time without knowing it.
[size=2]Darwinbots - [size=2]Artificial life simulation
Advertisement
The reference version, if the compiler generates naive code, creates pointer dereferences that are totally pointless. So yes, "it is slower". Of course, the optimizer could mutate that in any number of ways. In general, most people consider it bad form to pass by const ref unless copying the type is expensive.

As for constness, sometimes you don't want to change values by accident, even if other code isn't affected. You can just write const float var in those cases.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
A reference to a float is 32bits (assuming 32-bit os), so there is no point to the first method. Although in the end of the day it makes no difference, still I would avoid writing illogical code like that.
Hi,

using
void Func(const float &A, const float &B, const float &C)

might have a small overhead when the address of the float is taken and passed. I am not sure whether the compiler removes this or not.
But it is fact that using const references here is senseless and a misuse of the concepts behind them. They are intended for passing large objects which can then easiliy be accessed without the need to dereference them.

Quote:
Since the floats are passed by value in the latter method, you don't need consts and using consts is a "misuse of the concept of const-correctness."

I don't think it is a misuse but it is generally senseless.

Best regards,
Porthos
Quote:Original post by Promit
The reference version, if the compiler generates naive code, creates pointer dereferences that are totally pointless. So yes, "it is slower". Of course, the optimizer could mutate that in any number of ways. In general, most people
consider it bad form to pass by const ref unless copying the type is expensive.

As for constness, sometimes you don't want to change values by accident, even if other code isn't affected. You can just write const float var in those cases.


In addition to what promit said:

This is also a somewhat religious issue. For what its worth, I as well as the author of the book Effective C++ strongly prefer to pass basic types by value.
Depending on how the function is treated(inlined or not), the "float &A" is going to lead to extra instructions to copy the values back into
the associated variables from the calling function. But the "float A" doesn't need to copy anything back.
BUT you also tagged it with "const" which should tell the compiler that it doesn't need to copy anything back.
BUT there is still a chance it will treat the & as reason to pass a pointer, and thus will have to generate extra instructions to
store the address of the variable, then load up the variable once inside the function.

while you can write
void Foo(float A);
writing
void Bar(const float A);
can be important for compilers with bad optimization heuristics, since in Foo you could write A = A*2.0f;
while in Bar you've garenteed that you can't modify A in any way, and this could lead the compiler to optimize the function
differently.
I pass built-in types by value, applying const where appropriate. Also, on a 64-bit OS, those references will be twice the size of the floats so you're pushing and pulling twice as much data onto and off of the stack.

throw table_exception("(? ???)? ? ???");

Always use const when appropriate, you have nothing to lose by using it and everything to gain.
Pass-by-reference should be used for objects that are larger than the platform's registers, though this tends just to be the instrinsic types.

Quote:Original post by Ravyne
on a 64-bit OS, those references will be twice the size of the floats so you're pushing and pulling twice as much data onto and off of the stack.


Nit-picking here, but the size difference between a float and a float reference on 64-bit would make no difference as they'd both be stored in a 64-bit register and passed along 64-bit lines.

Deg.
Thanks everyone. I'll mend my wicked ways :)

Quote:Original post by Degra
Nit-picking here, but the size difference between a float and a float reference on 64-bit would make no difference as they'd both be stored in a 64-bit register and passed along 64-bit lines.


Is that true of, say, a char and a char& in a 32-bit environment? What about something like:

void Func(char A, char B, char C, char D)
[size=2]Darwinbots - [size=2]Artificial life simulation
The only way to determine this is to examine it in the context and look at generated assembly.

In many cases, the resulting code is surprisingly convoluted and has nothing in common with source code - yet is functionally identical.


As a general recommendation:
- pass basic types by value
- pass everything else by const reference

But now consider the modern optimizing compilers:
struct Message { int x, float y, std::string s, Vector3 pos };void foo( const Message & msg ){  std::cout << msg.x;}...void bar(){  Message myMessage;  myMessage.x = 10;  myMessage.y = 3.14;  myMessage.s = "Hello World";  myMessage.pos = ....;  foo(myMessage)}


Optimizing compilers will change the above code into:
void bar(){  std::cout << 10;  /y, s, pos do not even get initialized, since they have no side-effects}


Conclusion:

Unless you need to maintain some external API for linking/compatibility purposes, or are using virtual calls, fuctions get too mutilated to make any reasonable conclusion.

This topic is closed to new replies.

Advertisement