C++: Why can a statically created variable by passed to a function expecting a reference?

430 Views Asked by At

I've been programming in C++ for a while but certainly wouldn't call myself an expert. This question isn't being asked to solve a practical problem that I have, it's more about understanding what C++ is doing.

Imagine I have a function that expects a single paramater:

void doSomething(SomeClass& ref)
{
    // do something interesting
}

(Note: the parameter is a reference to SomeClass) Then I call the function like this:

int main(int argc, char *argv[])
{
    SomeClass a;
    doSomething(a);
}

Why is this legal C++? The function is expecting a reference to SomeClass, but I'm passing it a statically allocated variable of type SomeClass. A reference is like a pointer no? If we were to replace the reference with a pointer the compiler complains. Why is the reference different to a pointer in this way, what's going on behind the scenes?

Sorry if this is a stupid question, it's just been buggin me!

8

There are 8 best solutions below

0
On

A reference is nothing at all like a pointer, it is an alias - a new name - for some other object. That is one reason for having both!

Consider this:

Someclass a;

Someclass& b = a;
Someclass& c = a;

Here we first create an object a, and then we say that b and c are other names for the same object. Nothing new is created, just two additional names.

When b or c is a parameter to a function, the alias for a is made available inside the function, and you can use it to refer to the actual object.

It is that simple! You don't have to jump through any loops with &, *, or -> like when using pointers.

0
On

You're not passing it "a statically allocated variable of type SomeClass", you're passing it a reference to a SomeClass object you created on the stack in main().

Your function

void doSomething(SomeClass& ref)

Causes a reference to a in main to be passed in. That's what & after the type in the parameter list does.

If you left out the &, then SomeClass's (a's) copy constructor would be called, and your function would get a new, local copy of the a object in main(); in that case anything you did to ref in the function wouldn't be seen back in main()

0
On

Reference is not a pointer. You simply pass parameters as "by value" and use-it . Under the hood only a pointer will be used, but this is just under the hood.

0
On

I think you'd understand this better if you stopped thinking of references as being similar to pointers. I would say there are two reasons why this comparison is made:

  1. References allow you to pass objects into functions to allow them to be modified. This was a popular use case of pointers in C.
  2. The implementation of pointers and references is usually pretty much the same once it's compiled.

But they are different things. You could think about references as a way of giving a new name to an object. int& x = y; says that I want to give a new name to the object I currently refer to as y. This new name is x. Both of those identifies, x and y, both refer to the same object now.

This is why you pass the object itself as a reference. You are saying that you want the function to have its own identifier to refer the object that you are passing. If you don't put the ampersand in the parameter list, then the object will be copied into the function. This copying is often unnecessary.

0
On

Your code is incorrect - SomeClass a(); is a forward declaration of a function a returning a SomeClass instance - such a declaration is not valid at function scope.

Assuming you meant SomeClass a;:

A reference is quite similar to a pointer in most practical ways - the main difference is that you cannot legally have a reference to NULL, whereas you can have a pointer to NULL. As you've noticed, the syntax for pointers and references is different - you can't pass a pointer where a reference is expected.

If you think of a reference as a "pointer that can't be null and can't be made to point elsewhere" you're pretty much covered. You're passing something which refers to your local a instance - if doSomething modifies its parameter then it's really directly modifying your local a.

0
On
SomeClass a();

This is a function signature, not an object.

It should be

SomeClass a; // a is an object

Then your code is valid.

Why is this legal C++?

(assuming you fixed the previous point) C++ standard say that if your function attribute is a reference, then you should provide an object that have a name (an l-value). So here, it's legal. If it was a const reference, you could even provide a temporary (an r-value, that have no name).

The function is expecting a reference to SomeClass, but I'm passing it a statically allocated variable of type SomeClass.

It's expecting a reference to an non-const instance of SomeClass, that is what you did provide. That instance is not static, it's just allocated on the stack. The allocation of an object have nothing to do with the way it can be manipulated, only the scope does. The way the object is alloced (on the stack like here, or on the heap by using new/delete) only tells the lifetime of the object. Even a static object could be passed in your function, as far as it's not const.

I think you're mixing some language concepts here...

A reference is like a pointer no?

No.

A reference is a "nickname" of an object. No more, no less.

Okay, in fact it is implemented as a pointer with special rules but it's not true in every use: the compiler is free to implement it in whatever way it want. In case of a function attribute, it's often implemented as a pointer. But you don't have to even know about it.

For you, it's just the nickname of an object.

If we were to replace the reference with a pointer the compiler complains. Why is the reference different to a pointer in this way, what's going on behind the scenes?

I guess your first error did make things fuzzy for you?

0
On

Perhaps your confusion arises from the fact that if you have two functions:

void doThingA(int a) {
    a=23;
}
void doThingB(int &a) {
    a=23;
} 

The calls to them look the same, but are in fact very different:

int a=10;
doThingA(a);
doThingB(a);

The first case, doThingA(int), creates a completely new variable with the value 10, assignes 23 to it and returns. The original variable in the caller remains unchanged. In the second case, doThingB(int&), where the variable is passed by reference, a new variable is create with the same address as the variable passed in. This is what people mean when they say passing by reference is like passing by pointer. Because both variables share the same address (occupy the memory location) when doThingB(int&) changes the value passed in, the variable in the caller is also changed.

I like to think of it as passing pointer without all that annoying pointer syntax. Having said that though, I find functions that modify variables passed by reference to be confusing, and I almost never do it. I would either pass by const reference

void doThingB(const int &a);

or, if I want to modify the value, explicitly pass a pointer.

void doThingB(int *a);
0
On

Though the question has been already answered adequately, I can't resist sharing few more words on the related language feature of "References in C++".

As C programmers, we have two options available when passing variables to functions:

  1. Pass the value of the variable (creating a new copy)
  2. Pass the pointer to the variable.

When it comes to C++, we are usually dealing with objects. Copying such objects on each function call that needs to work on that object is not recommended due to space (and also speed) considerations. There are benefits involved with passing the variable address (via the pointer approach), and though we can make the pointer 'const' to avoid any changes through the pointer, the syntax with pointers is rather clumsy (miss the dereference operator at a place or two and end up spending hours debugging!).

C++, in providing 'references', packages the best of both options:

  1. The reference can be understood to be as good as passing the address
  2. The syntax to use the reference is the same as working on the variable itself.
  3. The reference would always point to 'something'. Hence no 'null-pointer' exceptions.

Additionally, if we make the reference 'const', we disallow any changes to the original variable.