Sign in to follow this  
icecubeflower

pass by reference

Recommended Posts

This question is kind of remedial and I could probably get by without knowing the answer but it's bothering me. Here's my test code:
#include <iostream>

void Intprinter(int inum);
void Intprinter2(int &inum);
void Intprinter3(int *inum);

int main()
{
  int *ival;
  int inum;

  ival=new int;
  *ival=5;
  inum=5;

  Intprinter(inum);
  Intprinter(*ival);
  Intprinter2(inum);
  //Intprinter2(&inum);
  //Intprinter2(ival);
  Intprinter2(*ival);
  Intprinter3(ival);
  Intprinter3(&inum);

  delete ival;
  return 0;
}

void Intprinter(int inum)
{
  std::cout<<inum<<"\n";
}

void Intprinter2(int &inum)
{
  std::cout<<inum<<"\n";
}

void Intprinter3(int *inum)
{
  std::cout<<*inum<<"\n"; 
}

Every line except for the two that are commented out output "5" to the screen. The ones that are commented out will not compile. It looks like Intprinter needs an integer value. So you can pass it an integer or you can dereference an int pointer and send the value. Intprinter3 expects an int pointer or the memory address of an int. So you can pass it an int pointer or you can reference a regular integer and send that. What I don't fully understand is Intprinter2 (pass by reference). It looks like all you can possibly send it is a regular integer or a dereferenced pointer, just like Intprinter(int inum). And the function itself somehow magically only takes the memory address of it. You can't send the function a referenced int or an int pointer, it expects a regular int no matter what. I guess what I don't understand is why does pass by reference even exist? Why not just make a function that expects a pointer like Intprinter3 and then just reference values before you pass them, like on my last call to Intprinter3? I mean isn't that the exact same thing? Although then you would have to dereference everything inside of the function.

Share this post


Link to post
Share on other sites
Quote:
Original post by icecubeflower
What I don't fully understand is Intprinter2 (pass by reference). It looks like all you can possibly send it is a regular integer. And the function itself somehow magically only takes the memory address of it. You can't send the function a referenced int or an int pointer, it expects a regular int no matter what.
Yes, a reference is internally handled like a pointer, but without having to dereference it.

Quote:
Original post by icecubeflower
I guess what I don't understand is why does pass by reference even exist? Why not just make a function that expects a pointer like Intprinter3 and then just reference values before you pass them, like on my last call to Intprinter3? I mean isn't that the exact same thing? Although then you would have to dereference everything inside of the function.
Reasons I can think of offhand:
1. You want to change the value of the parameter passed in, but that value is not optional. If it was optional you could use a pointer, and pass null as "I don't care about this parameter"
2. You can pass a parameter as const reference, which makes the function look tidier (No need for the address-of operator everywhere), and doesn't have the same overhead as using pass-by-value (Non-POD types will need a copy made to pass to the function which could be expensive - E.g. a std::vector with a few MB of memory allocated to it).

Moving this thread to For Beginners.

Share this post


Link to post
Share on other sites
While the C++ FQA has some valid points (since naturally C++ has problems), it also contains a whole lot of what seems utter rubbish, since the aim is to critisize every single aspect of C++ as well as every single question/answer on C++ FAQ (which is a very nice online learning material to widen your understanding of C++).

Quote:

Which means that you can't understand the effect of a statement as simple as a=b; without knowing whether a is a reference or not.


What does that mean? How does the fact that foo has an instance and bar has a reference change the meaning of a = b;


X b;
void foo(X a)
{
a = b;
//...
}

void bar(X& a)
{
a = b;
//...
}



In both cases the assignment does *exactly* the same thing, as the FAQ says (naturally the consequences to calling code would be different, but that would sort of be the intention of code like that).

Quote:

they make pointers to objects feel like objects, but for most purposes that can be achieved with typedef TStruct* T


I would really like an explanation how one would be able to write a generic algorithm for both vector and list (etc) without the concept of iterators that abstract away the differences in access to the underlying container...

Quote:

templates - a duplicate facility solving some of the problems of C macros and creating new, frequently more costly and complicated problems.


Duplicate?... One is simple token substitution without any idea of what the code means?

Quote:

Operator overloading is also useful for providing user-defined containers such as strings and vectors, duplicating the functionality of built-in strings and arrays.


What built-in strings (or even arrays which decay to pointers everywhere)? Am I missing something?

One could go on and on.

Share this post


Link to post
Share on other sites
Quote:
Original post by icecubeflower
I guess what I don't understand is why does pass by reference even exist?

To make operator overloading look nice.

a + b // nice
&a + &b // ugly

Share this post


Link to post
Share on other sites
Quote:
Original post by DevFred
Quote:
Original post by icecubeflower
I guess what I don't understand is why does pass by reference even exist?

To make operator overloading look nice.

a + b // nice
&a + &b // ugly


Wouldn't &a + &b just be adding the pointers to the pointers to a and b? Wouldn't you need to use *a + *b in this case?

@OP Why does any "syntactic sugar" exist? You could make the very same argument for classes and structs, if-then-else and the : ? operator, or even while and for.

They are functionally the same/interchangeable, but you can establish your own conventions for using one or the other to make things more readable.

Share this post


Link to post
Share on other sites
Quote:
Original post by MortusMaximus
Wouldn't &a + &b just be adding the pointers to the pointers to a and b?

No, you cannot add two pointers, that doesn't make sense. If there was no pass-by-reference, you'd have to pass the adresses of your objects to user-defined operator overloads.

Quote:
Original post by MortusMaximus
Wouldn't you need to use *a + *b in this case?

If a and b are objects of some user-defined type, the expressions *a and *b don't make any sense.

Share this post


Link to post
Share on other sites
Quote:

If there was no pass-by-reference, you'd have to pass the adresses of your objects to user-defined operator overloads.


And hence it would become impossible to write generic code that would work with user-defined and built-in types alike.

But then, in the spirit of C++ FQA you'd be using some kind of macros anyway instead of the "evil" templates. :)

Share this post


Link to post
Share on other sites
Quote:
Original post by MortusMaximus
Wouldn't &a + &b just be adding the pointers to the pointers to a and b? Wouldn't you need to use *a + *b in this case?
You're correct on both points, but I think DevFred was talking about the actual implementation of operator+, rather than what you would do in a pass-by-reference rather than pass-pointer-by-value function. That is, if there were no references, it would be necessary to implement operator overloading on pointers rather than values in order to maintain performance, resulting in ugly code.

Share this post


Link to post
Share on other sites
Quote:
Original post by icecubeflower
And the function itself somehow magically only takes the memory address of it. You can't send the function a referenced int or an int pointer, it expects a regular int no matter what.


The function call might be implemented in basically that way in the generated code, but the semantics are that you pass a value. The difference between passing by reference and passing by value is that when you pass by reference, you pass the value, rather than a copy of it.

Quote:
I guess what I don't understand is why does pass by reference even exist? Why not just make a function that expects a pointer like Intprinter3 and then just reference values before you pass them, like on my last call to Intprinter3?


1) Because taking addresses of things is extra work for the programmer.
2) Because taking addresses of things is error-prone.
3) Because for a given type T, a T* could be NULL, be invalid (uninitialized or pointing at memory that doesn't belong to the program or pointing at memory that doesn't represent a T), point to a valid single T, or point to an element of an array of T, but a T& can only refer to a valid single T. Making guarantees like this makes it easier to prove that code does the right thing.

4) Because
Quote:
then you would have to dereference everything inside of the function.


Share this post


Link to post
Share on other sites
Quote:
Original post by Sneftel
Quote:
Original post by MortusMaximus
Wouldn't &a + &b just be adding the pointers to the pointers to a and b?

You're correct on both points

No, he isn't. You cannot add two pointers in C++. You can add an int to a pointer and you can subtract pointers, but that's it. Adding pointers wouldn't make any sense.

Share this post


Link to post
Share on other sites
I'm not against pass by reference, I use it all the time. I use pointers all the time, too. It's just that I started thinking about it and reading about pointers and realized I'm using all these things and I still didn't have a clear understanding of them. I probably never fully got it until I wrote that silly program in the original post.

I mean before yesterday if somebody asked me if I knew what pass by reference was I would have been like, "Of course." And then if they asked me if it was the same as passing a pointer I would have said yes. But it's not. It's not passing a pointer or passing a value. It's passing a memory address of a value and then you can use the same syntax in the function as if it were passed by value.

I guess I was just thinking that pass by reference is like passing a pointer. So the & was saying pass the memory address. But then I started wondering why you could pass variables into it without referencing them. And then I realized I made lots of function where I pass actual pointers. And of course those function prototypes used *, not &. So then I started wondering, if & and * are opposites, then how could they mean the exact same thing in function prototypes. And then of course my whole belief system was shattered and I had a nervous breakdown. But then I wrote that program at the top to figure things out again so now everything is serene.

Share this post


Link to post
Share on other sites
You can sum up references with the following: Always prefer references to pointer, unless:

1) The value pointed to may be null
2) The value pointed to be not always exist (=null)
3) The value pointed to will need to change.
4) You need to do pointer math (Usually in the case of easily moving around an array)

In all other cases, you should prefer references to pointers. By doing this you gain the flexibility of pointers, but far fewer of the hassles. A reference, by definition, will always point to a valid object in memory. (Except in pathological cases)


Normally,

a->b // is it null? if i don't check it each time it could segfault
a.b // yay! it'll always work

Share this post


Link to post
Share on other sites
Quote:
Original post by smacdo
You can sum up references with the following: Always prefer references to pointer, unless:

1) The value pointed to may be null
2) The value pointed to be not always exist (=null)
3) The value pointed to will need to change.
4) You need to do pointer math (Usually in the case of easily moving around an array)

In all other cases, you should prefer references to pointers. By doing this you gain the flexibility of pointers, but far fewer of the hassles. A reference, by definition, will always point to a valid object in memory. (Except in pathological cases)


Normally,

a->b // is it null? if i don't check it each time it could segfault
a.b // yay! it'll always work


5) Use pointers when you need to allocate memory dynamicly ( i.e int *p = new int )

Other than what smacdo and others have said + the last reason i gave to use pointers, i cant think of any others. I always use referances when i need to pass something by value but its a user defined type ( ie. a class that holds color attributes ), eg.

void SetColor( const Color& color )
{
// set something to the specified color
}

You should never need to pass built in types by referance, my effective C++ book says that it can be slower performance wise if you do.

Share this post


Link to post
Share on other sites
Quote:
Original post by icecubeflower
Every line except for the two that are commented out output "5" to the screen. The ones that are commented out will not compile.

It looks like Intprinter needs an integer value. So you can pass it an integer or you can dereference an int pointer and send the value.
You've got things backwards if you expected the one having a reference as the parameter to accepts more things. Part of the deal is that it actually accepts less things.
For example.

Intprinter(42); // okay

Intprinter2(42); // wont compile


When the parameter is a reference, it can only accept an object that it is allowed to change. So it could change ival to 7, but it can't change 42 to be 7.
Const-reference on the other hand is another story.

Share this post


Link to post
Share on other sites
Quote:
Original post by CodeCriminal
5) Use pointers when you need to allocate memory dynamicly ( i.e int *p = new int )


Correct, though that falls under the first three rules. However in a game, this should not be an issue, because most code should *not* be allocating memory. Leave that to your resource manangers, and ensure that your resource managers are either

1) handing out references (be careful here, if you delete the resource the ref won't go OOS)
2) handing out smart pointers to those resources.

Share this post


Link to post
Share on other sites
pass by reference and pointers in general is need for dynamic data sets. If Pass by reference was included with c++ we would have a mess on our hands as programmers. References are not difficult to deal with and there syntax is quite nice i must add.

Share this post


Link to post
Share on other sites
Quote:

You can sum up references with the following: Always prefer references to pointer, unless:

1) The value pointed to may be null
2) The value pointed to be not always exist (=null)
3) The value pointed to will need to change.
4) You need to do pointer math (Usually in the case of easily moving around an array)

In all other cases, you should prefer references to pointers. By doing this you gain the flexibility of pointers, but far fewer of the hassles. A reference, by definition, will always point to a valid object in memory. (Except in pathological cases)


Normally,

a->b // is it null? if i don't check it each time it could segfault
a.b // yay! it'll always work


But with pass by reference you can still pass it a defreferenced pointer. So in the main program you could declare a pointer and make it point to NULL. Then you could dereference and pass it by reference. So the value may not always exist with pass by reference, either.

...well, I guess it does make sure that the value exists if it actually gets to the function. I guess if you try to dereference a NULL pointer the program will probably crash or something. Hold on...

Yeah, it seg faults. You can fail to allocate memory for a pointer or make it point at anything in particular, in which case whatever random memory it points at is what you pass. But if it points to NULL the pass by reference function is never called. The program seg faults when you try to dereference it. So... well, I guess you were right.

Share this post


Link to post
Share on other sites
Quote:
Original post by icecubeflower
I guess I was just thinking that pass by reference is like passing a pointer. So the & was saying pass the memory address. [...] And of course those function prototypes used *, not &. So then I started wondering, if & and * are opposites, then how could they mean the exact same thing in function prototypes.

The important thing to understand is that the symbol "&" means something completely different in the following two lines of code:

int &a = b; // here the & means that a is a reference (initialized to refer to b)
inc *c = &d; // here the & means "take the address of something", yielding a pointer

The fact that you see the same symbol "&" on both lines means absolutely nothing. In fact, there is even a third meaning to the same symbol:

int x = y & z; // here the & means bitwise-and


Quote:
Original post by icecubeflower
I guess if you try to dereference a NULL pointer

Welcome to undefined behavior land.

Share this post


Link to post
Share on other sites
Quote:

But with pass by reference you can still pass it a defreferenced pointer. So in the main program you could declare a pointer and make it point to NULL. Then you could dereference and pass it by reference. So the value may not always exist with pass by reference, either.


That is true (but covered before as a "pathological" case, as dereferencing a NULL pointer is already undefined behaviour and things can only go downhill from there on). References alone don't make your code safer if it doesn't come with other good practices: either code is designed so that there are no NULL pointers (they are always initialized to something) or if NULL is allowed, pointers should be always checked for NULL before dereferencing.

There are other ways of creating invalid references. I once fell for something like:


class X
{
Y& y_ref;
public:
X(Y& ref): y_ref(y_ref) {}
};




(C++ doesn't prohibit initializing a variable, including references, with itself?! Obviously I wanted to write y_ref(ref).

Another source is returning references from locals, but here the compiler should do a better job warning, and it shouldn't be that hard to understand the conditions where a function can and when it cannot return references. (Similarly you shouldn't return the address of a local variable.)


X& foo()
{
X x;
return x;
}




Then there's also the possibility that the original instance has been deallocated:


X* px = new X;
X& rx = *px;
delete px;
//rx dangling here



To deal with ownership and lifetime issues, one should probably turn to smart pointers. E.g, perhaps rx should instead by a boost::weak_ptr<X> here.

Share this post


Link to post
Share on other sites
Quote:
Original post by visitor

class X
{
Y& y_ref;
public:
X(Y& ref): y_ref(y_ref) {}
};

C++ doesn't prohibit initializing a variable, including references, with itself?!

But that's not what's happening here. You are not initializing y_ref with itself, but with the object that y_ref refers to. Remember, whenever a reference appears in an expression, it is just an alias for the referent.

Oh and having references as members is almost always a bad idea.

Share this post


Link to post
Share on other sites
That was a "practical" example, here's the most simple example. Not even a warning with GCC and -Wall.


int main()
{
int& a = a;
}




In the previous example, y_ref does not refer to anything before being initialized, and won't refer to anything after the initialization either. Using it lead to a crash.

Share this post


Link to post
Share on other sites
Quote:
Original post by visitor
Not even a warning with GCC and -Wall.


Though MSVC is nice enough to give you a C4700 for it. One of many reasons why it's good to test your code on multiple compilers.

Share this post


Link to post
Share on other sites
Quote:

But if it points to NULL the pass by reference function is never called. The program seg faults when you try to dereference it. So... well, I guess you were right.

Not necessarily. The most important thing to understand about undefined behaviour is that literally anything can happen. This includes the program appearing to work just fine!

Dereferencing NULL is in no way guaranteed to cause a segmentation fault. In practise, it almost always will. But we could construct a program where NULL is dereferenced in the source and, due to which branches are taken at run time, no crash occurs.

In the following program, compiled using MSVC 2008 in Debug mode, the NULL pointer dereference is not where the crash occurs. The crash occurs when the "foo" reference is used. If this line is removed, the program completes.

struct Foo { int x; };

void function(Foo &foo)
{
std::cout << "entering \"function\"\n";
foo.x = 42;
std::cout << "leaving \"function\"\n";
}

int main()
{
Foo *ptr = 0;
std::cout << "calling \"function\"\n";
function(*ptr);
}


However, we cannot say that we have created a "NULL" reference. There is no such thing. Once the NULL pointer was dereferenced, the program behaviour ceases to have any meaning. Simply put: you cannot draw conclusions about C++ from an ill-formed program.

That is, of course, in theory. In practise, bugs will arise and you will have to deal with invalid references. Most large C++ programs probably invoke undefined behaviour in their normal operation. It is important to understand that even though one can say "there is no such thing as an invalid reference" and be 100% correct in theory, that this statement is also 100% false in practise.

Remember kids: in theory, theory works.

Share this post


Link to post
Share on other sites
Quote:

Though MSVC is nice enough to give you a C4700 for it. One of many reasons why it's good to test your code on multiple compilers.


That is true, but warnings are sort of optional for compiler implementers.

(In the example with the constructor initialization list GCC warns about argument not used. Initializing something with itself could be required to be a syntax error by the standard, but then I guess there could be quite a lot of once-in-a-lifetime mistakes/stupidities that could be explicitly made illegal by the standard.)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this