3

I'm reading the book C++ Primer Plus (6th Edition) and I've come across something that is kind of confusing to me, so bear with me while I try to explain...

If I have a function whose prototype looks like so:

void cube(double x);

and another function who prototype looks like so:

void cube(const double &x);

what is the difference between the two? For the first function prototype, the value is passed by value meaning that it will be copied and thus unaltered by the function. For the second prototype, the value is passed by reference but it's a constant reference so C++ will create an anonymous temporary variable and assign the value of the argument to the temporary variable thus mimicking pass by value. So, in essence there is really no difference between the two function prototypes, right? What is the point of (const double &x) then?

Drew Dormann
  • 59,987
  • 13
  • 123
  • 180
Jonathan
  • 2,623
  • 3
  • 23
  • 38
  • it will not create temporary variable, you will pass in address of that double, with gunaratee that unless the caller is drugged, it cant be null(you will get exception thrown I believe if you try to dereference null reference) and that you can use . instead of ->. Additionally, you cant alter contents stored inside x – Creris Jul 19 '15 at 15:55
  • I like how you describe, in detail, the differences between the two. Then you say "so there's no difference between the two, right?" Also you're not quite accurate in your description: there is no "anonymous temporary" going on here. – Lightness Races in Orbit Jul 19 '15 at 16:04

6 Answers6

4

For the second prototype, the value is passed by reference but it's a constant reference so C++ will create an anonymous temporary variable and assign the value of the argument to the temporary variable thus mimicking pass by value.

Although C++ would do this in certain situations. For example, if you pass an expression returning double, C++ will create a temporary:

double v = 123.456;
cube(5*v+321.0123);

However, it would not necessarily do that. For example, if you make this call

double v = 123.456;
cube(v);

C++ would pass a reference to v directly to the cube() function. In this case, concurrent modifications to v would be "visible" to the cube() function while it is running.

So, in essence there is really no difference between the two function prototypes, right?

That's right, there isn't much difference between the two. Assuming that double takes the same amount of space as a reference to double, there would be no difference in performance as well.

What is the point of (const double &x) then?

Although there is little difference between passing double by value or by constant reference, you may get considerable difference when dealing with other types, such as std::string. Taking parameters by constant reference becomes very useful when you code an algorithm as a template, i.e. when you have to write

void cube(const T& v);

and your T could be any type. Constant reference approach lets you control the amount of copying that is going on, because starting at a certain object size, passing a reference becomes much cheaper than passing a copy.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • I'm a little confused by this response. If you pass by value, the functions receives a copy of the variable that it's allowed to edit. If you pass by constant reference, regardless of what's happening under the hood, semantically the function receives a reference to an object that it cannot modify. That seems like a big difference. Am I just missing the point, and the question is really focused on exactly how the value is passed? – mock_blatt Jul 19 '15 at 16:07
  • Very well explained. Thank you very much. – Jonathan Jul 19 '15 at 16:16
  • 1
    There is an important difference: If you pass by const reference, the caller can modify the reference while your code is running (e.g., from another thread, or when your code calls out to another function). This alters the value inside your function. Passing by value gives you a copy that nobody can modify. – Raymond Chen Jul 19 '15 at 16:16
  • @RaymondChen Thanks for the correction! I added a clarification to the explanation. – Sergey Kalinichenko Jul 19 '15 at 16:21
  • No difference in performance, is that actually true? I would expect that there would be extra indirection when you pass by const reference potentially. – Nir Friedman Jul 19 '15 at 18:25
  • 1
    @NirFriedman True, there's an extra level of indirection here, so cycle-for-cycle comparison would probably be in favor of passing a copy. Once you factor in the effects of cache, however, I think the difference would be hard to measure, because accessing the const reference can be heavily optimized. – Sergey Kalinichenko Jul 19 '15 at 20:39
4

For the following two prototypes

void cube(double x);

and

void cube(const double &x);

the behavior will be the same from the perspective of the caller and the callee in that neither will allow modification to propagate to the caller. However, in the latter (const reference) example, x will be passed by reference instead of by value. If double were large enough, or if it was a larger data type (e.g., struct) passing by reference would avoid copying a lot of data (since only the pointer is passed).

The second-order performance effects (e.g., effects on cache weighed against the copy of the data) are all very implementation-dependent, and depend not only upon the processor / cache architecture and compiler, but on the structure and runtime use case of the program.

rholmes
  • 4,064
  • 3
  • 25
  • 34
1

For primitive types, you don't gain anything by using a const&. const& is traditionally used for big objects to avoid possibly expensive and unnecessary copying.

You also need const& if the type does not support copying yet you want to disallow modification of the referenced object.

With the advent of C++11, move semantics question the traditional wisdom of "pass small objects by value, big objects by const reference" As far as I can see, the C++ expert community has not yet reached a new consensus on this topic. For example, see How true is "Want Speed? Pass by value".

Community
  • 1
  • 1
Christian Hackl
  • 27,051
  • 3
  • 32
  • 62
1

Well, there is some difference:

  • With void cube(double x), you're making a local copy of variable x in your function, and you can change its value without changing the value of the original x. So, in the local function void cube, you can interact with x as with ordinal variable.
  • With void cube(const double& x), you're just passing a pointer (in c++, reference is a pointer but with slightly another syntax of usage), and const here means that you can't change the value of the variable in this address (that the pointer is pointed on). So, in the local function void cube, you should interact with x as with a constant variable.

What about difference in performance?

With double there's no difference in performance, because double takes 64 bits, and the reference to double takes 32 or 64 bits, not much difference. But, imagine you have a struct:

struct some_very_big_struct {
    ...
}

where sizeof(some_very_big_struct) is 2^10 bytes, so making a copy of this struct takes really much time and additional memory, so in that case passing a reference is the best choice.

Chan Kha Vu
  • 9,834
  • 6
  • 32
  • 64
  • sizes are implementation defined. For instance, with Visual Studio when compiling for x86 sizeof(double) == 8 and sizeof(double*) == 4 – Creris Jul 19 '15 at 16:16
  • @Creris Oh, I assumed that OP is using 64 bit system. Edited, thanks ^^ – Chan Kha Vu Jul 19 '15 at 16:23
1

The standard clearly requires, subject to the rules in [dcl.init.ref] #5.1, that the reference parameter to be bound to the argument, as opposed (just for example) to the reference to be bound to some copy. The difference is detectable, so no "as if" rule is applicable (in all cases).

double a;
void foo(const double &x) { assert(&a == &x); }
void bar() { foo(a); }

There is a practical difference too. In the case of const double &x, the name x is just an alias for some double that may be accessed via other lvalues, which has a lot of implications to optimisation (like register saves/restores, instruction reordering, etc), for example:

  double a;
  void foo(const double &x) {
       ... = x;
       a = bar(); // here the compiler must assume that the assignment
                  // may modify x, the two statements can't be reordered
  }

  void foo(double x) {
       ... = x;
       a = bar(); // here the compiler knows that the assignment
                  // cannot modify x, and the two statements can be
                  // reordered
  }
chill
  • 16,470
  • 2
  • 40
  • 44
-2

Although the read/write ops would slow it down, a reference/pointer would only be 32 bits on 32bit machine, where a double would be 64 bits. That doesn't play as well with a 32 bit word size.

It makes no sense, really. An optimising compiler will probably treat them the same and generate identical code.

EDIT: To explain myself, I should have emphasised "probably".

Strict standards compliance is currently a bad way to judge whether or not a compiler is indeed a C++ compiler - compilers often abuse/ignore the standard in favour of optimisations and syntactic sugar.

As for the (performance) difference, I was trying to say measuring the cost of passing a double (over the word size) to the function VS the cost of fetching from a pointer/reference (word size) - and in most cases it's negligible. The larger the value you're passing, the faster fetching from pointer/reference will become.
Casey Muratori mentioned this, and encourages passing structs slightly over the word size.

Raz0r
  • 73
  • 1
  • 6