• Advertisement
Sign in to follow this  

Question about passing and returning refrences

This topic is 1907 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

So I created a function and I wanted to know if there were any differences between these 2:


1)

Datatype& Get(int x, int y)
{
return m_array[y * width + x];
}


2)

Datatype Get( int &x, int &y)
{
return m_array[y * width + x];
}


personally, my c++ book taught me method 2

Share this post


Link to post
Share on other sites
Advertisement
There are some fundamental differences here that I believe are important to discuss. However, before we look at the differences, let's break down the programs:

Program 1:


Datatype& Get(int x, int y)
{
return m_array[y * width + x];
}
[/quote]

This program declares an function named Get() that returns a reference to a datatype (Another note, you should probably make this method constant, because it doesn't change any values of the class it is in.). It's important to note that it returns a reference to the datatype. Normally, a program simply makes a copy of the value stored at whatever memory address your return value's at and returns that value. However, when you pass by reference, you're giving me the address of the actual variable you're returning.

The function takes two integers, x and y, as parameters. These integers are copied. What that means is that instead of using the actual integers, your program is simply making a copy of the value stored at the memory address stored at the integers, which the program uses. You can see this if you try to change x's value inside the function, and then print out x's value after calling the function. If these integers were passed by reference, than changing the value of x or y would change the actual value.

Program 2:


Datatype Get( int &x, int &y)
{
return m_array[y * width + x];
}
[/quote]

This program declares a function called Get() which takes two references to int's, x and y. Get() returns a copy of the value in m_array[y * width + x]. This function, unlike the above one, returns a value, and not a reference. That means that instead of giving the actual address of m_array[y * width + x], it simply makes a copy of the value stored there and returns that.

Instead of taking in copies of integers, the function passes in two references to integers. This means it's taking in the actual address, so if you modify one of the variables in the program, it changes the actual variable. Since you're not changing the value of x and y or setting a pointers address to them, there's no reason to pass by reference here or in the above program. You might want to return a reference to Datatype, and you must if you're setting a pointer' address to the return value of the function.

I hope this helped you understand references and pointers with far more detail!

Share this post


Link to post
Share on other sites
I think method 2 is bad. You don't change x and y inside the function so they should either be passed in as value "int x" or as const references "const int &x", definitely not as references "int &x". And with basic types like int, my understanding is that there is no reason to use const references; passing by value is equally fast or faster.

Share this post


Link to post
Share on other sites

There are some fundamental differences here that I believe are important to discuss. However, before we look at the differences, let's break down the programs:

Program 1:


Datatype& Get(int x, int y)
{
return m_array[y * width + x];
}


This program declares an function named Get() that returns a reference to a datatype (Another note, you should probably make this method constant, because it doesn't change any values of the class it is in.).[/quote]

That bit about making the method const is not right. If the caller only has a constant reference to the class, he shouldn't be able to modify the contents of m_array, but this function returns a reference to an element of m_array and can therefore be used to change the contents.

You can provide a const version that returns by value [or const reference] and a non-const version that returns by reference. That's what I would do, in part because it's consistent with the behavior of std::vector's operator[]. Edited by Álvaro

Share this post


Link to post
Share on other sites

[quote name='superman3275' timestamp='1354464994' post='5006305']
There are some fundamental differences here that I believe are important to discuss. However, before we look at the differences, let's break down the programs:

Program 1:


Datatype& Get(int x, int y)
{
return m_array[y * width + x];
}


This program declares an function named Get() that returns a reference to a datatype (Another note, you should probably make this method constant, because it doesn't change any values of the class it is in.).[/quote]

That bit about making the method const is not right. If the caller only has a constant reference to the class, he shouldn't be able to modify the contents of m_array, but this function returns a reference to an element of m_array and can therefore be used to change the contents.

You can provide a const version that returns by value [or const reference] and a non-const version that returns by reference. That's what I would do, in part because it's consistent with the behavior of std::vector's operator[].
[/quote]

Wow, I never even considered that :)! That's why I enjoy reading the veteran posters comments, they make me consider more than before.

Share this post


Link to post
Share on other sites
Let us step back and consider what references in function signatures are used for. References essentially allow one to modify non-global data from another scope.

The first function allows the caller to modify the data stored in the array:

class Foo {
public:
void modify() { /* ... */ }

// ...
};

Foo &foo = grid.Get(2, 3);

// The Foo instance inside the grid is modified
foo.modify();


The second allows the callee (i.e. Get()) to modify the variables passed in by the caller:

int x = 2;
int y = 3;

Foo foo = grid.Get(x, y);

// x and y might have different values now - if the implementation of Get() were modified


Note that passing by reference prevents the callee passing values:

// This will not compile because Get() takes non-const references
Foo foo = list.Get(2, 3);


Now, let us thing about how references are actually used. As return types, they allow the caller direct access to objects returned from the function - typically member variables but potentially access to global data or heap allocated data.

Here, std::string::at() returns a reference - allowing the contents to be changed.

std::string s = "Hello, World.";
// Prints "Hello, world."
std::cout << s << std::endl;

s.at(12) = '!';
// Prints "Hello, world!"
std::cout << s << std::endl;


While the actually type and signature is more complex - conceptually, at() looks like this:

namespace std {
class string {
public:
char &at(int index);
// ...
};
}

I chose at() because it is simpler to see as a function than operator[].

We can also write such functions as "const", by changing the return type to a const reference and marking the member function as const. The following example takes std::vector, which is a resizeable array, as the reference.

Note how the function can take a const reference to the monster in the vector, and call a "const" method on it.

template<typename T>
class vector {
public:
T&at(int index);
const T&(int index) const;
// ...
};

class Monster {
public:
bool healthy() const;
// ...
};

void describeNextMonster(const std::vector<Monster> &monsters) {
if(monsters.empty()) {
std::cout << "There are no more monsters." << std::endl;
} else {
const Monster &monster = monsters.at(0);
if(monster.healthy()) {
std::cout << "The next monster looks strong..." << std::endl;
} else {
std::cout << "The next monster appears weak!" << std::endl;
}
}
}

Again, the standard is being simplified to avoid complecting the example.

Generally, one should avoid returning non-const references to an objects internals, unless like std::vector<> the point of the class is to expose these things. I wouldn't recommend, for example, exposing the monster's heart:

class Monster {
public:
// Note: This is often a sign of poor design!
Heart &heart() {
return m_heart;
}

private:
Heart m_heart;
// ...
};

With this code, clients can call monster.heart().pierce(), which is usually not a good way to do things. Exposing const access to an objects internals is slightly better, but still should be considered carefully. Sometimes it is necessary, for instance if the game is wants to display the player's health status as a picture of the human body, it might need to be able to read the player's vital statistics directly.

Now, let us turn to reference parameters. There are two common use cases. The first is to avoid copying expensive objects. For instance, if you write a function that takes a copy of a std::string or a std::vector by value, what happens if someone passes in an entire book as a string, or a vector will 1,000,000 large elements? In most cases, a copy is unnecessary so we can pass a const reference to the string or vector instead. The function might take a lot longer to operate than it might if the copy were avoided.

In C++, a simple rule of thumb is to pass all non-native values by const reference, though see the final note for a particular caveat about this.

The other case where you use reference parameters is to modify the caller's variables. The most common reason for doing this is to return more than one value without creating a custom class or structure. For example, to simultaneously return something or have an error code:

bool readInput(std::string &str);

// ...

std::string str;
if(readInput(str)) {
// Use "str"
} else {
// Handle the error
}


The final note to be aware of is that references depend on the original object, so you can run into trouble:

void afterBattle(Monster &monster) {
if(!monster.healthy()) {
const Heart &heart = monster.heart();
if(heart.failing()) {
Heart replacement = findPigHeart();
heartSurgery(monster, replacement);
}

// We should not access "heart" here, as we may be referring to the old heart
// The old heart would likely be destroyed as part of the operator
}
}


This is an example of why direct access to the object's internals can be a design flaw. In this case, it is easier to write a buggy program than if the monster class did not allow the heart to be directly accessed. This is an alternative, where the monster class provides additional member functions, but no longer allows access to its fields via references:

void after_battle(Monster &monster) {
if(monster.needsHeartSurgery()) {
Heart replacement = findPigHeart();
monster.replaceHeart(replacement);
}
}


There are other ways of invalidating references, and not all can be designed around. It is not hard to invalidate references in standard containers and strings, for example, and there is little the classes can do to help you because by design they expose this detail.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement