Thought Leadership

C++ reference parameters – the downside

By Colin Walls

Something that I have discovered over the years is a great pleasure. When I am giving information – presenting, teaching, writing an article or a blog – it is not necessarily a one-way process. I often receive useful and interesting information back. I have commented that I learn as much from delivering a class as I might from attending one.

I was recently talking about C++ for embedded at a conference and considering some of the features that I felt resulted in better, clearer and more bug-free code. One of these was the option to pass parameters by reference. Then someone explained a drawback of this feature …

Pointers are a very powerful feature of the C language and largely explain its wide usage for embedded programming. But they can also be confusing and error prone. For example, consider this code:

int *p;
p = (int *)0x8000;
p += 1;   // this could equally be p=p+1 or p++

What is the final value of p?

Assuming the compiler in use regards integers as 32 bits, the answer is 0x8004.

Although, to the experienced programmer, this may be crystal clear, it is far from obvious to someone coming to the language for the first time.

Of course, C++ has pointers too and this example behaves in the same way, but C++ has some opportunities to avoid the use of pointers and, hence, write clearer code. And one of those is the option to pass parameters by reference.

This code might be written in C, for example:

void swap(int *a, int *b)
{
   int temp = *a;
   *a = *b;
   *b = temp;
}

The parameters are pointers to data in the calling function and this code is littered with pointer operations, every one of which is a potential error. So, in C++ we can pass the parameters by reference, thus:

void swap(int& a, int& b)
{
   int temp = a;
   a = b;
   b = temp;
}

Now, the calling function does not need to provide [explicit] pointers and the pointer operations are eliminated in the swap() function, rendering it more readable and less likely to contain a bug.

This is all fine, but the problem that was pointed out to me was with a call to swap(). In C, the call would look like this:

swap(&x, &y);

It is 100% clear that pointers have been passed and, hence, the possibility that the values of x and y might be changed by the call to swap() is quite clear. However, in C++, if reference parameters were used, the call would be written like this:

swap(x, y);

And there is no clear indication, without resort to the function prototype [which would, of course, be readily on hand if you are using a good IDE], that x and y could get changed.

I guess it is a case of “what you win on the swings, you lose on the roundabouts”.

Comments

0 thoughts about “C++ reference parameters – the downside
  • quote> “swap(&x, &y);
    It is 100% clear that pointers have been passed and, hence, the possibility that the values of x and y might be changed by the call to swap() is quite clear.”

    How is it any clearer with the pointers than references? If you don’t know the function prototype, you don’t know whether the function takes const or non-const pointer, or whether there is a conversion happening, or even whether operator& was overloaded. In other words, if you don’t know what function is doing and how it is defined, all guesses can be wrong, no matter whether one uses pointers, or references, or values.

  • Colin,

    You’re right, and this is another example which illustrates that reading C++ code & knowing exactly what is going on is not as straightforward as it is with C. Operator overloading is another example of this.

    Having said that – there is a situation where references — specifically, changing call-by-value to call-by-reference, is very handy.

    I worked with a customer that had many simple/small classes, and they would often pass by value. There is some rule of thumb, depends on the architecture, but typically most shops will say if a class is 8 bytes or smaller, just pass by value.

    Fast forward, the client refactored the classes, and some information to each, and now the objects were bigger — sometimes up to 24 bytes. Note that this code base was around 20,000 files.

    Well, in C, you’d not only have to change the called function’s interface (probably from value to pointer-to-const), but you’d also have to change every place where the functions are called.

    However, with references, you only have to change the called functions (reference-to-const probably), but the *calling code* doesn’t need to be changed at all. Of course, it needs to be re-compiled, but the expensive part – the human labor to go through and change ~40,000 function calls (which you’d have to do in C), that cost you don’t incur.

  • Gene:
    Your points are 100% valid, if you are writing new code – you do need to see the prototype of a function you’re going to call. However, I was thinking of what engineers do an enormous amount of the time: reading code. In this case, you can see from the call [in C] exactly what’s going on.

    Dan:
    Good input. Thanks. It seems to me that you could tune the size/performance of an application by simply tweaking a a few function definitions/prototypes in C++, which would not be possible in C.

  • Colin,

    You may have missed what Gene was saying. You say, “In this case, you can see from the call [in C] exactly whatโ€™s going on.” But Gene’s point is that that is not true. You’re adding extra assumptions to the C version that you’re not granting to the C++ version.

    Consider this C prototype:

    void swap( const int * a, int * b );

    Calling that function would use the same “clear” syntax you prefer:

    swap( &x, &y );

    but its meaning is *not* clear to someone reading the C code. You still need to know what swap() does.

    Now one can argue that this is a perversely named function and that any reasonable person would justly assume that a function called swap simply swaps the values of the arguments. But then we’re invoking the semantics of the name of the function, and presumably the reference-based function should get the same benefit-of-the-doubt.

    That is to say, without adding further information (e.g., a function prototype, naming standards, etc.), swap(x,y) is really of exactly the same actual clarity as swap(&x,&y).

    • Sorry Matthew, but I have to beg to differ. If I see a function being sent a pointer to something, although I don’t necessarily know what the function is going to do, at least I am alerted to the possibility that the thing pointed to may be changed. With a reference parameter, I don’t get any warning.

  • I agree with you when you say you can detect “the *possibility* that the thing pointed to may be changed,” but not when you said you could see “*exactly* what’s going on.” There is really no certainty in either case without added information, so to my mind the alleged downside of reference parameters is at least mitigated if not eliminated altogether. Cheers!

  • PS, If you want the same sort of hint (not clarity ๐Ÿ™‚ ) that C gives, you could use a free wrapper — free in the sense that the compiler will optimize it away. For example, this code should work fine:

    void swap( int& a, int& b );
    int i = 10, j = 42;
    swap( ref(i), ref(j) );

    Indeed, there is a std::ref function was part of the C++ committee’s TR1 library extensions and is also part of C++0x (the new version of the C++ standard). It is already available from many C++ vendors such as Microsoft (VS2008 and up) and GNU, which are the two tool chains for my current embedded projects (one of which targets a rather dinky processor inside an FPGA). Specifically, std::ref() and std::tr1::ref() are in , , or similar.

  • Oops, it doesn’t display my angle-bracketed headers. The last line should have had “functional, tr1/function,” but with angle brackets around them.

Leave a Reply

This article first appeared on the Siemens Digital Industries Software blog at https://blogs.sw.siemens.com/embedded-software/2011/02/21/c-reference-parameters-the-downside/