7

I've always been told that we should not pass POD by reference. But recently I've discovered that a reference actually takes no memory at all.

So why do we choose to write:

void DoSomething(int iNumber);

instead of:

void DoSomething(const int& riNumber);

is it not more efficient?

xcrypt
  • 3,276
  • 5
  • 39
  • 63
  • check this [question](http://stackoverflow.com/questions/270408/is-it-better-in-c-to-pass-by-value-or-pass-by-constant-reference) out – Tiago Sep 26 '11 at 21:45
  • 2
    Who told you that a reference takes no memory? That's not universally true, in fact a reference parameter to a non-inline function does take extra memory. – Ben Voigt Sep 26 '11 at 21:46
  • 1
    There's a difference between built-in types and PODs. There's no limit (that I know of) to how much memory a POD class can take. If it's large, a reference is definitely a good idea. For example, the Windows API frequently makes use of large POD types. – Mooing Duck Sep 26 '11 at 21:47
  • 1
    Since references are generally syntactic sugar for pointers, I'm pretty sure a reference will take around `sizeof(void *)` bytes on the stack. It all depends on your implementation, of course, but reference are not made with magic dust. – Etienne de Martel Sep 26 '11 at 21:48
  • because in some cases `const int&` is twice as big as `int` by value. POD can generally be pass by value unless you actually want a non-const reference to a previously instantiated variable so the function can change it's value. – AJG85 Sep 26 '11 at 21:54
  • related: http://stackoverflow.com/questions/2139224/how-to-pass-objects-to-functions-in-c/2139254#2139254 – sbi Sep 26 '11 at 21:57
  • @Mooing Duck What's the diff between built in types and POD? I've got so many confusions :( – xcrypt Sep 26 '11 at 22:04
  • @xcrypt: `struct Foo {int bigarray[1024][1024];}` is POD. `int` is a built in type. The first is something you *never* want to pass around by value. The latter is something you almost always want to pass around by value. Multiple programming standards (e.g., Google's) forbids passing builtins by reference, and not on the grounds of efficiency, but on the grounds of it being confusing/dangerous (not that I necessarily endorse this POV). – David Hammen Sep 26 '11 at 22:09
  • @xcrypt: The details differ between C++03 and C++11, but it's roughly: `POD types are built-in-primitives, and classes without constructors/assignment-operators/destructors/virtual-functions/static-members, with all public members, who's member-objects and base-classes are all also POD types.` – Mooing Duck Sep 26 '11 at 22:09
  • Also, I found it in the standard. The official name for "built-in-types" are "Fundamental types" – Mooing Duck Sep 26 '11 at 22:13
  • Whether or not a reference requires storage is *unspecified*. – fredoverflow Sep 26 '11 at 23:24

8 Answers8

9

Actually in this case (using int) passing by value is probably more efficient, since only 1 memory-read is needed instead of 2, to access the passed value.

Example (optimized using -O2):

int gl = 0;

void f1(int i)
{
    gl = i + 1;
}

void f2(const int& r)
{
    gl = r + 1;
}

int main()
{
    f1(1);

    f2(1);
}

Asm

    .file   "main.cpp"
    .text
    .p2align 2,,3
.globl __Z2f1i
    .def    __Z2f1i;    .scl    2;  .type   32; .endef
__Z2f1i:
LFB0:
    pushl   %ebp
LCFI0:
    movl    %esp, %ebp
LCFI1:
    movl    8(%ebp), %eax
    incl    %eax
    movl    %eax, _gl
    leave
    ret
LFE0:
    .p2align 2,,3
.globl __Z2f2RKi
    .def    __Z2f2RKi;  .scl    2;  .type   32; .endef
__Z2f2RKi:
LFB1:
    pushl   %ebp
LCFI2:
    movl    %esp, %ebp
LCFI3:
    movl    8(%ebp), %eax
    movl    (%eax), %eax
    incl    %eax
    movl    %eax, _gl
    leave
    ret
LFE1:
    .def    ___main;    .scl    2;  .type   32; .endef
    .p2align 2,,3
.globl _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
LFB2:
    pushl   %ebp
LCFI4:
    movl    %esp, %ebp
LCFI5:
    andl    $-16, %esp
LCFI6:
    call    ___main
    movl    $2, _gl
    xorl    %eax, %eax
    leave
    ret
LFE2:
.globl _gl
    .bss
    .align 4
_gl:
    .space 4
Truncheon
  • 916
  • 2
  • 12
  • 25
  • 1
    Could you explain this a bit more? It has to be dereferenced? I never actually knew that... I'm getting very confused – xcrypt Sep 26 '11 at 21:53
  • The value of the int resides in memory which means 1 memory read. Adding a pointer to the mix means you have to read what the pointer is pointing at and then read from that memory address (2 memory reads). –  Sep 26 '11 at 22:01
  • @Mike Are you really sure a reference is actually a pointer? I've thought this too in the past until I asked my C++ teacher, who told me a reference takes 0 bytes and is comparable to accessing the same variable by just typing a different name – xcrypt Sep 26 '11 at 22:08
  • @Mike. That makes the assumption that you are passing parameters on the stack (not true for C++ code were parameters can be passed in the register). It also assumes the code is not inlined (then again it falls through). The whole model of thinking like this leads to a whole host of misconceptions about what the compiler is doing and usually don't hold. – Martin York Sep 26 '11 at 22:08
  • 3
    @xcrypt: Your teachers statement holds true were the reference is used locally to the value it refers too and is just an alias for another variable. But when you pass things around between functions that have no concept of where the value cam e from you need to pass around something that represents the original object (internally this is usually a pointer). – Martin York Sep 26 '11 at 22:12
4

Not passing PODs by reference seems like a too general rule. PODs can be huge, and passing references to it would be worth. In your particular case, an int is the same size than the pointer that most -if not all- implementations use in the background to actually implement references. There is no size difference between passing an int or a reference, but now you have the penalty of an extra level of indirection.

K-ballo
  • 80,396
  • 20
  • 159
  • 169
2

Passing by reference is passing by pointer in disguise.

With small data, it can be faster to access it as values rather than having to deference the pointer several times.

Pubby
  • 51,882
  • 13
  • 139
  • 180
  • I don't think there are modern architectures that will pass a value faster then a reference. – long404 Sep 26 '11 at 21:51
  • @long404: Could you back up your claim that passing an int won't be faster than taking a pointer to such int -or a temporary if needed- and passing such pointer? – K-ballo Sep 26 '11 at 21:57
  • @long404 Well of course not. Value will have to push more onto the stack, but this will likely be faster than deferencing your reference pointer each time you access it. Reference passes faster (for structs), value accesses faster. For single ints you should always pass by value when possible. – Pubby Sep 26 '11 at 21:58
  • @k-ballo: Sure. As Pubby8 said it is a pointer in disguise and copying it takes as much as copying a single machine word. Usually that is a single instruction and often the registers are used for such parameters. Do you know of a case when something gets copied faster then that? – long404 Sep 26 '11 at 22:04
  • @Pubby8: Absolutely agreed (especially if you have to change pages). But the statement was for "passing it" not accessing it. – long404 Sep 26 '11 at 22:05
  • @long404: A '`value`' or a POD does not need to be the size of a machine word, it could be an aggregate of many other types. – K-ballo Sep 26 '11 at 22:07
  • @long404: On the typical 64 bit machine, an int and a "word" are still 32 bits long but a reference argument takes 64 bits, twice the size of an int. – David Hammen Sep 26 '11 at 22:07
  • @David Hammen: I don't think anything smaller then 64 bit on a 64bit architecture takes less time to pass. – long404 Sep 26 '11 at 22:13
  • @K-ballo: absolutely. but your comment states (I quote): "Could you back up your claim that passing an int won't be faster than taking a pointer to such int". And with int it should be the same with anything that is 32+bit. – long404 Sep 26 '11 at 22:15
  • @long404: Got me there, however I still think that passing a pointer would be (barely) slower; taking a pointer to the value requires an extra operation. And if the argument is a literal, it requires moving it into a temporary variable. – K-ballo Sep 26 '11 at 22:19
  • @k-ballo: agreed. passing it takes the same time, BUT as pubby stated "accessing" the reference will be slower. i fully agree with that! – long404 Sep 26 '11 at 22:20
1

Because it is a meaningless efficiency gain 99.999% of time, and it changes the semantics. It also prevents you from passing in a constant value. For example:

void Foo(int &i) { }
Foo(1);  // ERROR!

This would work however:

void Foo(const int &i) { }
Foo(1);

Also, it means that the function can modify the value of i such that it is visible to the caller, which may well be a bad thing (but again, you could certainly take a const reference). It comes down to optimizing the parts of your program where it matters and making the semantics of the rest of the code as correct as possible.

a reference actually takes no memory at all.

Not sure who told you that, but it's not true.

Ed S.
  • 122,712
  • 22
  • 185
  • 265
  • agreed. the reference is just a const pointer, thus it takes the size of a machine word. – long404 Sep 26 '11 at 21:50
  • 1
    Re *a reference actually takes no memory at all.*: That can be true when the reference is a local automatic variable. Then the reference is just an alias. When the reference is an argument to a function or a data member in a class, it is better to look at a reference as just a pointer in disguise. – David Hammen Sep 26 '11 at 22:04
  • @DavidHammen local automatic variable? Could you show me an example? – xcrypt Sep 26 '11 at 22:12
  • @xcrypt: `void foo () { int bar = 42; int & bar_ref = bar; ... }` Here `bar_ref` truly can be just an alias to `bar`. The compiler is free to skip creating any extra memory for `bar_ref`. It is also free to create memory for `bar_ref`; how compilers implement references is up to the compiler vendor. The standard doesn't care so long as the compiler does exhibit the correct behaviors. – David Hammen Sep 26 '11 at 22:20
  • @David: Right, but of course, the question was asked in the context of taking a reference as an argument to a function. – Ed S. Sep 26 '11 at 22:21
  • @Ed S.: xcrypt asked for an example, in the context of *a reference actually takes no memory at all*. So I gave one. – David Hammen Sep 26 '11 at 23:10
  • @David, oh, I somehow missed that comment, sorry. – Ed S. Sep 26 '11 at 23:16
1

Depends what you mean by efficiency.

The main reason we pass objects by constant reference is because doing so by value will invoke the object's copy constructor, and that can be an expensive operation.

Copying an int is trivial.

Etienne de Martel
  • 34,692
  • 8
  • 91
  • 111
1

There is no right answer, or even obvious general preference. Passing by value can be faster in some instances, and very importantly, can eliminate side effects. In other cases, passing by reference can be faster, and allow information to be more readily returned from a given function. This holds for POD data types. Keep in mind, a struct can be a POD, so size considerations vary between PODs even.

loki11
  • 374
  • 1
  • 4
1

The real answer is habit.
We have an ingrained culture of attempting macro optimizations on our code (even if it rarely makes any real difference). I would be surprised if you can show me code were passing by value/reference (an integer) makes any difference.

Once you start hiding the type via template we start using const reference again (because it may be expensive to copy some types).

Now if you had asked about generic POD then there could be a cost difference as POD can get quite large.

There is small advantage in the first version that we do not need an extra identifier if we are mutating the original value.

void DoSomething(int iNumber)
{
    for(;iNumber > 0; --iNumber)   // mutating iNumber
    {
       // Stuff
    }
}

// No real cost difference in code.
// Just in the amount of text we need to read to understand the code
//
void DoSomething(int const& iNumber)
{
    for(int loop = iNumber;loop > 0; --loop)   // mutating new identifier
    {
       // Stuff
    }
}
Martin York
  • 257,169
  • 86
  • 333
  • 562
0

No it is not. The example with int is simply the same. It matters when you have "heavier" objects.

long404
  • 1,037
  • 9
  • 12